From 8091baeb6ef2d4bd37d7845928bdaf516cc301d0 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Mon, 2 Dec 2024 16:56:35 +0100 Subject: [PATCH 01/83] Restrict Stripe Link to credit card payment method and improve cleanup (#9822) Co-authored-by: Timur Karimov --- changelog/fix-stripe-link-button | 4 +++ client/checkout/blocks/payment-processor.js | 22 ++++++++++++-- client/checkout/stripe-link/index.js | 19 ++++++++++-- .../checkout/stripe-link/test/index.test.js | 29 +++++++++++++++++++ 4 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 changelog/fix-stripe-link-button diff --git a/changelog/fix-stripe-link-button b/changelog/fix-stripe-link-button new file mode 100644 index 00000000000..d8acf0626f1 --- /dev/null +++ b/changelog/fix-stripe-link-button @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Restrict Stripe Link to credit card payment method and improve cleanup. diff --git a/client/checkout/blocks/payment-processor.js b/client/checkout/blocks/payment-processor.js index ee7ef6af861..952470aa46b 100644 --- a/client/checkout/blocks/payment-processor.js +++ b/client/checkout/blocks/payment-processor.js @@ -28,7 +28,10 @@ import { useCustomerData } from './utils'; import enableStripeLinkPaymentMethod from 'wcpay/checkout/stripe-link'; import { getUPEConfig } from 'wcpay/utils/checkout'; import { validateElements } from 'wcpay/checkout/classic/payment-processing'; -import { PAYMENT_METHOD_ERROR } from 'wcpay/checkout/constants'; +import { + PAYMENT_METHOD_ERROR, + PAYMENT_METHOD_NAME_CARD, +} from 'wcpay/checkout/constants'; const getBillingDetails = ( billingData ) => { return { @@ -70,6 +73,7 @@ const PaymentProcessor = ( { const stripe = useStripe(); const elements = useElements(); const hasLoadErrorRef = useRef( false ); + const linkCleanupRef = useRef( null ); const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); const isTestMode = getUPEConfig( 'testMode' ); @@ -81,7 +85,10 @@ const PaymentProcessor = ( { } = useCustomerData(); useEffect( () => { - if ( isLinkEnabled( paymentMethodsConfig ) ) { + if ( + activePaymentMethod === PAYMENT_METHOD_NAME_CARD && + isLinkEnabled( paymentMethodsConfig ) + ) { enableStripeLinkPaymentMethod( { api: api, elements: elements, @@ -123,11 +130,22 @@ const PaymentProcessor = ( { } ); }, onButtonShow: blocksShowLinkButtonHandler, + } ).then( ( cleanup ) => { + linkCleanupRef.current = cleanup; } ); + + // Cleanup the Link button when the component unmounts + return () => { + if ( linkCleanupRef.current ) { + linkCleanupRef.current(); + linkCleanupRef.current = null; + } + }; } }, [ api, elements, + activePaymentMethod, paymentMethodsConfig, setBillingAddress, setShippingAddress, diff --git a/client/checkout/stripe-link/index.js b/client/checkout/stripe-link/index.js index 45f4feabf36..e44cf0d8236 100644 --- a/client/checkout/stripe-link/index.js +++ b/client/checkout/stripe-link/index.js @@ -4,6 +4,13 @@ import { dispatchChangeEventFor } from '../utils/upe'; export const switchToNewPaymentTokenElement = () => { + // Switch to card payment method before enabling new payment token element + document + .querySelector( + 'input[name="payment_method"][value="woocommerce_payments"]' + ) + ?.click(); + const newPaymentTokenElement = document.getElementById( 'wc-woocommerce_payments-payment-token-new' ); @@ -44,16 +51,17 @@ const enableStripeLinkPaymentMethod = async ( options ) => { const emailField = document.getElementById( options.emailId ); if ( ! emailField ) { - return; + return Promise.resolve( () => null ); } const stripe = await options.api.getStripe(); // https://stripe.com/docs/payments/link/autofill-modal const linkAutofill = stripe.linkAutofillModal( options.elements ); - emailField.addEventListener( 'keyup', ( event ) => { + const handleKeyup = ( event ) => { linkAutofill.launch( { email: event.target.value } ); - } ); + }; + emailField.addEventListener( 'keyup', handleKeyup ); options.onButtonShow( linkAutofill ); @@ -65,6 +73,11 @@ const enableStripeLinkPaymentMethod = async ( options ) => { ); switchToNewPaymentTokenElement(); } ); + + return () => { + emailField.removeEventListener( 'keyup', handleKeyup ); + removeLinkButton(); + }; }; export default enableStripeLinkPaymentMethod; diff --git a/client/checkout/stripe-link/test/index.test.js b/client/checkout/stripe-link/test/index.test.js index b8d907c3508..392a170b179 100644 --- a/client/checkout/stripe-link/test/index.test.js +++ b/client/checkout/stripe-link/test/index.test.js @@ -85,6 +85,35 @@ describe( 'Stripe Link elements behavior', () => { expect( handleButtonShow ).toHaveBeenCalled(); } ); + test( 'Should properly clean up when cleanup function is called', async () => { + createStripeLinkElements(); + const billingEmailInput = document.getElementById( 'billing_email' ); + const removeEventListenerSpy = jest.spyOn( + billingEmailInput, + 'removeEventListener' + ); + const removeLinkButtonSpy = jest.spyOn( + document.querySelector( '.wcpay-stripelink-modal-trigger' ), + 'remove' + ); + + const cleanup = await enableStripeLinkPaymentMethod( { + api: WCPayAPI(), + emailId: 'billing_email', + onAutofill: () => null, + onButtonShow: () => null, + } ); + + // Call the cleanup function + cleanup(); + + expect( removeEventListenerSpy ).toHaveBeenCalledWith( + 'keyup', + expect.any( Function ) + ); + expect( removeLinkButtonSpy ).toHaveBeenCalled(); + } ); + function createStripeLinkElements() { // Create the input field const billingEmailInput = document.createElement( 'input' ); From f5007901465c4d1018fe05e16af39d6dffbfedf0 Mon Sep 17 00:00:00 2001 From: bruce aldridge Date: Tue, 3 Dec 2024 10:34:07 +1300 Subject: [PATCH 02/83] Update inquiry order notes to use inquiry specific content (#9828) Co-authored-by: Nagesh Pai Co-authored-by: Nagesh Pai <4162931+nagpai@users.noreply.github.com> --- changelog/fix-9612-inquiry-order-note | 4 ++ includes/class-wc-payments-order-service.php | 48 +++++++++++-- ...wc-payments-webhook-processing-service.php | 3 +- .../test-class-wc-payments-order-service.php | 71 ++++++++++++++++++- ...wc-payments-webhook-processing-service.php | 3 +- 5 files changed, 119 insertions(+), 10 deletions(-) create mode 100644 changelog/fix-9612-inquiry-order-note diff --git a/changelog/fix-9612-inquiry-order-note b/changelog/fix-9612-inquiry-order-note new file mode 100644 index 00000000000..3fce0a23430 --- /dev/null +++ b/changelog/fix-9612-inquiry-order-note @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Order notes for inquiries have clearer content. diff --git a/includes/class-wc-payments-order-service.php b/includes/class-wc-payments-order-service.php index 195dbe115a7..c563877e830 100644 --- a/includes/class-wc-payments-order-service.php +++ b/includes/class-wc-payments-order-service.php @@ -314,15 +314,17 @@ public function mark_order_blocked_for_fraud( $order, $intent_id, $intent_status * @param string $amount The disputed amount – formatted currency value. * @param string $reason The reason for the dispute – human-readable text. * @param string $due_by The deadline for responding to the dispute - formatted date string. + * @param string $status The status of the dispute. * * @return void */ - public function mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by ) { + public function mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by, $status = '' ) { if ( ! is_a( $order, 'WC_Order' ) ) { return; } - $note = $this->generate_dispute_created_note( $charge_id, $amount, $reason, $due_by ); + $is_inquiry = strpos( $status, 'warning_' ) === 0; + $note = $this->generate_dispute_created_note( $charge_id, $amount, $reason, $due_by, $is_inquiry ); if ( $this->order_note_exists( $order, $note ) ) { return; } @@ -346,7 +348,8 @@ public function mark_payment_dispute_closed( $order, $charge_id, $status ) { return; } - $note = $this->generate_dispute_closed_note( $charge_id, $status ); + $is_inquiry = strpos( $status, 'warning_' ) === 0; + $note = $this->generate_dispute_closed_note( $charge_id, $status, $is_inquiry ); if ( $this->order_note_exists( $order, $note ) ) { return; @@ -1643,15 +1646,32 @@ private function generate_fraud_blocked_note( $order ): string { * @param string $amount The disputed amount – formatted currency value. * @param string $reason The reason for the dispute – human-readable text. * @param string $due_by The deadline for responding to the dispute - formatted date string. + * @param bool $is_inquiry Whether the dispute is an inquiry or not. * * @return string Note content. */ - private function generate_dispute_created_note( $charge_id, $amount, $reason, $due_by ) { + private function generate_dispute_created_note( $charge_id, $amount, $reason, $due_by, $is_inquiry = false ) { $dispute_url = $this->compose_dispute_url( $charge_id ); // Get merchant-friendly dispute reason description. $reason = WC_Payments_Utils::get_dispute_reason_description( $reason ); + if ( $is_inquiry ) { + return sprintf( + WC_Payments_Utils::esc_interpolated_html( + /* translators: %1: the disputed amount and currency; %2: the dispute reason; %3 the deadline date for responding to the inquiry */ + __( 'A payment inquiry has been raised for %1$s with reason "%2$s". Response due by %3$s.', 'woocommerce-payments' ), + [ + 'a' => '', + ] + ), + $amount, + $reason, + $due_by, + $dispute_url + ); + } + return sprintf( WC_Payments_Utils::esc_interpolated_html( /* translators: %1: the disputed amount and currency; %2: the dispute reason; %3 the deadline date for responding to dispute */ @@ -1672,15 +1692,31 @@ private function generate_dispute_created_note( $charge_id, $amount, $reason, $d * * @param string $charge_id The ID of the disputed charge associated with this order. * @param string $status The status of the dispute. + * @param bool $is_inquiry Whether the dispute is an inquiry or not. * * @return string Note content. */ - private function generate_dispute_closed_note( $charge_id, $status ) { + private function generate_dispute_closed_note( $charge_id, $status, $is_inquiry = false ) { $dispute_url = $this->compose_dispute_url( $charge_id ); + + if ( $is_inquiry ) { + return sprintf( + WC_Payments_Utils::esc_interpolated_html( + /* translators: %1: the dispute status */ + __( 'Payment inquiry has been closed with status %1$s. See payment status for more details.', 'woocommerce-payments' ), + [ + 'a' => '', + ] + ), + $status, + $dispute_url + ); + } + return sprintf( WC_Payments_Utils::esc_interpolated_html( /* translators: %1: the dispute status */ - __( 'Payment dispute has been closed with status %1$s. See dispute overview for more details.', 'woocommerce-payments' ), + __( 'Dispute has been closed with status %1$s. See dispute overview for more details.', 'woocommerce-payments' ), [ 'a' => '', ] diff --git a/includes/class-wc-payments-webhook-processing-service.php b/includes/class-wc-payments-webhook-processing-service.php index 0cad2ffe950..d9b0333c765 100644 --- a/includes/class-wc-payments-webhook-processing-service.php +++ b/includes/class-wc-payments-webhook-processing-service.php @@ -535,6 +535,7 @@ private function process_webhook_dispute_created( $event_body ) { $reason = $this->read_webhook_property( $event_object, 'reason' ); $amount_raw = $this->read_webhook_property( $event_object, 'amount' ); $evidence = $this->read_webhook_property( $event_object, 'evidence_details' ); + $status = $this->read_webhook_property( $event_object, 'status' ); $due_by = $this->read_webhook_property( $evidence, 'due_by' ); $order = $this->wcpay_db->order_from_charge_id( $charge_id ); @@ -558,7 +559,7 @@ private function process_webhook_dispute_created( $event_body ) { ); } - $this->order_service->mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by ); + $this->order_service->mark_payment_dispute_created( $order, $charge_id, $amount, $reason, $due_by, $status ); // Clear dispute caches to trigger a fetch of new data. $this->database_cache->delete( DATABASE_CACHE::DISPUTE_STATUS_COUNTS_KEY ); diff --git a/tests/unit/test-class-wc-payments-order-service.php b/tests/unit/test-class-wc-payments-order-service.php index d7c6ca45c0d..2eeaa50864e 100644 --- a/tests/unit/test-class-wc-payments-order-service.php +++ b/tests/unit/test-class-wc-payments-order-service.php @@ -867,6 +867,44 @@ public function test_mark_payment_dispute_created() { $this->assertCount( 2, $notes_2 ); } + + /** + * Tests if the payment was updated to show inquiry created. + */ + public function test_mark_payment_dispute_created_for_inquiry() { + // Arrange: Set the charge_id and reason, and the order status. + $charge_id = 'ch_123'; + $amount = '$123.45'; + $reason = 'product_not_received'; + $deadline = 'June 7, 2023'; + $order_status = Order_Status::ON_HOLD; + $dispute_status = 'warning_needs_response'; + + // Act: Attempt to mark payment dispute created. + $this->order_service->mark_payment_dispute_created( $this->order, $charge_id, $amount, $reason, $deadline, $dispute_status ); + + // Assert: Check that the order status was updated to on-hold status. + $this->assertTrue( $this->order->has_status( [ $order_status ] ) ); + + $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + + // Assert: Check that dispute order note was added with relevant info and link to dispute detail. + $this->assertStringNotContainsString( 'Payment has been disputed', $notes[0]->content ); + $this->assertStringContainsString( 'inquiry', $notes[0]->content ); + $this->assertStringContainsString( $amount, $notes[0]->content ); + $this->assertStringContainsString( 'Product not received', $notes[0]->content ); + $this->assertStringContainsString( $deadline, $notes[0]->content ); + $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">Response due by', $notes[0]->content ); + + // Assert: Check that order status change note was added. + $this->assertStringContainsString( 'Pending payment to On hold', $notes[1]->content ); + + // Assert: Applying the same data multiple times does not cause duplicate actions. + $this->order_service->mark_payment_dispute_created( $this->order, $charge_id, $amount, $reason, $deadline, $dispute_status ); + $notes_2 = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + $this->assertCount( 2, $notes_2 ); + } + /** * Tests to make sure mark_payment_dispute_created exits if the order is invalid. */ @@ -909,7 +947,7 @@ public function test_mark_payment_dispute_closed_with_status_won() { // Assert: Check that the notes were updated. $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); $this->assertStringContainsString( 'Pending payment to Completed', $notes[1]->content ); - $this->assertStringContainsString( 'Payment dispute has been closed with status won', $notes[0]->content ); + $this->assertStringContainsString( 'Dispute has been closed with status won', $notes[0]->content ); $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">dispute overview', $notes[0]->content ); // Assert: Applying the same data multiple times does not cause duplicate actions. @@ -937,7 +975,7 @@ public function test_mark_payment_dispute_closed_with_status_lost() { // Assert: Check that the notes were updated. $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); $this->assertStringContainsString( 'On hold to Refunded', $notes[1]->content ); - $this->assertStringContainsString( 'Payment dispute has been closed with status lost', $notes[0]->content ); + $this->assertStringContainsString( 'Dispute has been closed with status lost', $notes[0]->content ); $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">dispute overview', $notes[0]->content ); // Assert: Check for created refund, and the amount is correct. @@ -951,6 +989,35 @@ public function test_mark_payment_dispute_closed_with_status_lost() { $this->assertCount( 3, $notes_2 ); } + + /** + * Tests if the order note was added to show inquiry closed. + */ + public function test_mark_payment_dispute_closed_with_status_warning_closed() { + // Arrange: Set the charge_id, dispute status, the order status, and update the order status. + $charge_id = 'ch_123'; + $status = 'warning_closed'; + $order_status = Order_Status::COMPLETED; + $this->order->update_status( Order_Status::ON_HOLD ); // When a dispute is created, the order status is changed to On Hold. + + // Act: Attempt to mark payment dispute created. + $this->order_service->mark_payment_dispute_closed( $this->order, $charge_id, $status ); + + // Assert: Check that the order status was left in on-hold status. + $this->assertTrue( $this->order->has_status( [ $order_status ] ) ); + + // Assert: Check that the notes were updated. + $notes = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + $this->assertStringNotContainsString( 'Dispute has been closed with status won', $notes[0]->content ); + $this->assertStringContainsString( 'inquiry', $notes[0]->content ); + $this->assertStringContainsString( '%2Fpayments%2Ftransactions%2Fdetails&id=ch_123" target="_blank" rel="noopener noreferrer">payment status', $notes[0]->content ); + + // Assert: Applying the same data multiple times does not cause duplicate actions. + $this->order_service->mark_payment_dispute_closed( $this->order, $charge_id, $status ); + $notes_2 = wc_get_order_notes( [ 'order_id' => $this->order->get_id() ] ); + $this->assertCount( 3, $notes_2 ); + } + /** * Tests to make sure mark_payment_dispute_closed exits if the order is invalid. */ diff --git a/tests/unit/test-class-wc-payments-webhook-processing-service.php b/tests/unit/test-class-wc-payments-webhook-processing-service.php index d376b1491fd..2acb5c318ad 100644 --- a/tests/unit/test-class-wc-payments-webhook-processing-service.php +++ b/tests/unit/test-class-wc-payments-webhook-processing-service.php @@ -1240,6 +1240,7 @@ public function test_dispute_created_order_note() { 'charge' => 'test_charge_id', 'reason' => 'test_reason', 'amount' => 9900, + 'status' => 'test_status', 'evidence_details' => [ 'due_by' => 'test_due_by', ], @@ -1289,7 +1290,7 @@ public function test_dispute_closed_order_note() { ->method( 'add_order_note' ) ->with( $this->matchesRegularExpression( - '/Payment dispute has been closed with status test_status/' + '/Dispute has been closed with status test_status/' ) ); From 55fa477ea43e1ca722ba4892a5f8afe24c211731 Mon Sep 17 00:00:00 2001 From: bruce aldridge Date: Tue, 3 Dec 2024 10:34:36 +1300 Subject: [PATCH 03/83] Prevent browser error on dispute evidence submission (#9847) Co-authored-by: Nagesh Pai Co-authored-by: Jessy Pappachan <32092402+jessy-p@users.noreply.github.com> --- ...x-9830-browser-error-on-dispute-submission | 4 +++ client/disputes/evidence/index.js | 25 +++++++++++++------ 2 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 changelog/fix-9830-browser-error-on-dispute-submission diff --git a/changelog/fix-9830-browser-error-on-dispute-submission b/changelog/fix-9830-browser-error-on-dispute-submission new file mode 100644 index 00000000000..918ad744351 --- /dev/null +++ b/changelog/fix-9830-browser-error-on-dispute-submission @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Browser error no longer shows after dispute evidence submission diff --git a/client/disputes/evidence/index.js b/client/disputes/evidence/index.js index 17501b3c3b0..940c78c865e 100644 --- a/client/disputes/evidence/index.js +++ b/client/disputes/evidence/index.js @@ -455,6 +455,8 @@ export default ( { query } ) => { const [ dispute, setDispute ] = useState(); const [ loading, setLoading ] = useState( false ); const [ evidence, setEvidence ] = useState( {} ); // Evidence to update. + const [ redirectAfterSave, setRedirectAfterSave ] = useState( false ); + const { createSuccessNotice, createErrorNotice, @@ -475,7 +477,7 @@ export default ( { query } ) => { ); const confirmationNavigationCallback = useConfirmNavigation( () => { - if ( pristine ) { + if ( pristine || redirectAfterSave ) { return; } @@ -488,6 +490,7 @@ export default ( { query } ) => { useEffect( confirmationNavigationCallback, [ pristine, confirmationNavigationCallback, + redirectAfterSave, ] ); useEffect( () => { @@ -603,11 +606,6 @@ export default ( { query } ) => { const message = submit ? __( 'Evidence submitted!', 'woocommerce-payments' ) : __( 'Evidence saved!', 'woocommerce-payments' ); - const href = getAdminUrl( { - page: 'wc-admin', - path: '/payments/disputes', - filter: 'awaiting_response', - } ); recordEvent( submit @@ -639,9 +637,20 @@ export default ( { query } ) => { ], } ); - window.location.replace( href ); + setRedirectAfterSave( true ); }; + useEffect( () => { + if ( redirectAfterSave && pristine ) { + const href = getAdminUrl( { + page: 'wc-admin', + path: '/payments/disputes', + filter: 'awaiting_response', + } ); + window.location.replace( href ); + } + }, [ redirectAfterSave, pristine ] ); + const handleSaveError = ( err, submit ) => { recordEvent( submit @@ -690,8 +699,8 @@ export default ( { query } ) => { }, } ); setDispute( updatedDispute ); - handleSaveSuccess( submit ); setEvidence( {} ); + handleSaveSuccess( submit ); updateDisputeInStore( updatedDispute ); } catch ( err ) { handleSaveError( err, submit ); From dc6f74cb48b4da74d0ba37e6709570b2dd51d243 Mon Sep 17 00:00:00 2001 From: Shendy <73803630+shendy-a8c@users.noreply.github.com> Date: Tue, 3 Dec 2024 04:35:45 +0700 Subject: [PATCH 04/83] Fix styling on payment details page in mobile view. (#9790) --- .../fix-7230-payments-details-mobile-view | 4 + .../components/dispute-status-chip/index.tsx | 4 +- .../components/payment-status-chip/index.js | 4 +- .../dispute-details/dispute-notice.tsx | 7 +- .../dispute-details/style.scss | 14 + .../test/__snapshots__/index.test.tsx.snap | 22 +- client/payment-details/summary/index.tsx | 66 +++-- client/payment-details/summary/style.scss | 22 +- .../test/__snapshots__/index.test.tsx.snap | 264 +++++++++++------- .../test/__snapshots__/index.test.tsx.snap | 43 ++- 10 files changed, 280 insertions(+), 170 deletions(-) create mode 100644 changelog/fix-7230-payments-details-mobile-view diff --git a/changelog/fix-7230-payments-details-mobile-view b/changelog/fix-7230-payments-details-mobile-view new file mode 100644 index 00000000000..93e179a44ca --- /dev/null +++ b/changelog/fix-7230-payments-details-mobile-view @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix styling of transaction details page in mobile view. diff --git a/client/components/dispute-status-chip/index.tsx b/client/components/dispute-status-chip/index.tsx index 393089a8a78..dde601f3de7 100644 --- a/client/components/dispute-status-chip/index.tsx +++ b/client/components/dispute-status-chip/index.tsx @@ -23,11 +23,13 @@ interface Props { status: DisputeStatus | string; dueBy?: CachedDispute[ 'due_by' ] | EvidenceDetails[ 'due_by' ]; prefixDisputeType?: boolean; + className?: string; } const DisputeStatusChip: React.FC< Props > = ( { status, dueBy, prefixDisputeType, + className, } ) => { const mapping = displayStatus[ status ] || {}; let message = mapping.message || formatStringValue( status ); @@ -50,7 +52,7 @@ const DisputeStatusChip: React.FC< Props > = ( { type = 'alert'; } - return ; + return ; }; export default DisputeStatusChip; diff --git a/client/components/payment-status-chip/index.js b/client/components/payment-status-chip/index.js index b26da2ad4ed..fb57843849b 100755 --- a/client/components/payment-status-chip/index.js +++ b/client/components/payment-status-chip/index.js @@ -11,11 +11,11 @@ import displayStatus from './mappings'; import Chip from '../chip'; import { formatStringValue } from 'utils'; -const PaymentStatusChip = ( { status } ) => { +const PaymentStatusChip = ( { status, className } ) => { const mapping = displayStatus[ status ] || {}; const message = mapping.message || formatStringValue( status ); const type = mapping.type || 'light'; - return ; + return ; }; export default PaymentStatusChip; diff --git a/client/payment-details/dispute-details/dispute-notice.tsx b/client/payment-details/dispute-details/dispute-notice.tsx index 9d9edd8a9f7..d2c8b00fbf2 100644 --- a/client/payment-details/dispute-details/dispute-notice.tsx +++ b/client/payment-details/dispute-details/dispute-notice.tsx @@ -64,7 +64,12 @@ const DisputeNotice: React.FC< DisputeNoticeProps > = ( { { createInterpolateElement( sprintf( noticeText, shopperDisputeReason ), { - a: , + a: ( + + ), strong: , } ) } diff --git a/client/payment-details/dispute-details/style.scss b/client/payment-details/dispute-details/style.scss index 60773396b54..b68be2d0a20 100644 --- a/client/payment-details/dispute-details/style.scss +++ b/client/payment-details/dispute-details/style.scss @@ -182,3 +182,17 @@ margin-bottom: 0; } } + +.dispute-notice { + .dispute-notice__link { + display: block; + } +} + +@media screen and ( max-width: $break-small ) { + .wcpay-inline-notice.components-notice + .components-notice__content + a.dispute-notice__link { + white-space: normal; + } +} 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 9fa0098691b..7b4a7e3650f 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 @@ -30,21 +30,25 @@ exports[`Order details page should match the snapshot - Charge without payment i
-

- $15.00 - - USD - + $15.00 + + USD + +

Pending -

+
diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx index 261645c8a85..06699f07147 100644 --- a/client/payment-details/summary/index.tsx +++ b/client/payment-details/summary/index.tsx @@ -257,37 +257,41 @@ const PaymentDetailsSummary: React.FC< PaymentDetailsSummaryProps > = ( {
-

- - { formattedAmount } - - { charge.currency || 'USD' } - - { charge.dispute ? ( - - ) : ( - - ) } - -

+
+

+ + { formattedAmount } + + { charge.currency || 'USD' } + + +

+ { charge.dispute ? ( + + ) : ( + + ) } +
{ renderStorePrice ? (

diff --git a/client/payment-details/summary/style.scss b/client/payment-details/summary/style.scss index ef67a756663..0c6c7775733 100755 --- a/client/payment-details/summary/style.scss +++ b/client/payment-details/summary/style.scss @@ -11,7 +11,7 @@ .payment-details-summary { display: flex; flex: 1; - @include breakpoint( '<660px' ) { + @media screen and ( max-width: $break-medium ) { flex-direction: column; } @@ -19,13 +19,29 @@ flex-grow: 1; } + .payment-details-summary__amount-wrapper { + display: flex; + align-items: center; + } + + @media screen and ( max-width: $break-small ) { + .payment-details-summary__amount-wrapper { + flex-direction: column; + align-items: flex-start; + + .payment-details-summary__status { + order: -1; + } + } + } + .payment-details-summary__amount { @include font-size( 32 ); font-weight: 300; padding: 0; margin: 0; display: flex; - flex-wrap: wrap; + flex-wrap: nowrap; align-items: center; .payment-details-summary__amount-currency { @@ -93,7 +109,7 @@ justify-content: initial; flex-direction: column; flex-wrap: nowrap; - @include breakpoint( '>660px' ) { + @media screen and ( min-width: $break-medium ) { justify-content: flex-start; align-items: flex-end; } 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 8286a7941bb..083da902f05 100644 --- a/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/summary/test/__snapshots__/index.test.tsx.snap @@ -26,21 +26,25 @@ exports[`PaymentDetailsSummary capture notification and fraud buttons renders ca

-

- $20.00 - - usd - + $20.00 + + usd + +

Payment authorized -

+
@@ -362,21 +366,25 @@ exports[`PaymentDetailsSummary capture notification and fraud buttons renders th
-

- $20.00 - - usd - + $20.00 + + usd + +

Needs review -

+
@@ -708,21 +716,25 @@ exports[`PaymentDetailsSummary correctly renders a charge 1`] = `
-

- $20.00 - - usd - + $20.00 + + usd + +

Paid -

+
@@ -1029,21 +1041,25 @@ exports[`PaymentDetailsSummary correctly renders when payment intent is missing
-

- $20.00 - - usd - + $20.00 + + usd + +

Paid -

+
@@ -1336,21 +1352,25 @@ exports[`PaymentDetailsSummary order missing notice does not render notice if or
-

- $20.00 - - usd - + $20.00 + + usd + +

Paid -

+
@@ -1657,21 +1677,25 @@ exports[`PaymentDetailsSummary order missing notice renders notice if order miss
-

- $20.00 - - usd - + $20.00 + + usd + +

Paid -

+
@@ -2001,21 +2025,25 @@ exports[`PaymentDetailsSummary renders a charge with subscriptions 1`] = `
-

- $20.00 - - usd - + $20.00 + + usd + +

Paid -

+
@@ -2349,21 +2377,25 @@ exports[`PaymentDetailsSummary renders fully refunded information for a charge 1
-

- $20.00 - - usd - + $20.00 + + usd + +

Refunded -

+
@@ -2647,19 +2679,23 @@ exports[`PaymentDetailsSummary renders loading state 1`] = `
-

- - - USD - + + + USD + +

-

+
@@ -2887,21 +2923,25 @@ exports[`PaymentDetailsSummary renders partially refunded information for a char
-

- $20.00 - - usd - + $20.00 + + usd + +

Partial refund -

+
@@ -3211,21 +3251,25 @@ exports[`PaymentDetailsSummary renders the Tap to Pay channel from metadata 1`]
-

- $20.00 - - usd - + $20.00 + + usd + +

Paid -

+
@@ -3532,21 +3576,25 @@ exports[`PaymentDetailsSummary renders the information of a dispute-reversal cha
-

- $20.00 - - usd - + $20.00 + + usd + +

Disputed: Won -

+
diff --git a/client/payment-details/test/__snapshots__/index.test.tsx.snap b/client/payment-details/test/__snapshots__/index.test.tsx.snap index 0798e4b5d52..aa8a34effd0 100644 --- a/client/payment-details/test/__snapshots__/index.test.tsx.snap +++ b/client/payment-details/test/__snapshots__/index.test.tsx.snap @@ -30,16 +30,25 @@ exports[`Payment details page should match the snapshot - Charge query param 1`]
-

+

+ + Amount placeholder + +

- Amount placeholder + Paid -

+
@@ -493,21 +502,25 @@ exports[`Payment details page should match the snapshot - Payment Intent query p
-

- $1,500.00 - - usd - + $1,500.00 + + usd + +

Paid -

+
From 04f1e4c8fc70103da77096cedf8897c9de254bde Mon Sep 17 00:00:00 2001 From: Shendy <73803630+shendy-a8c@users.noreply.github.com> Date: Tue, 3 Dec 2024 05:11:39 +0700 Subject: [PATCH 05/83] Remove payout timing notice and update the help tooltip. (#9812) --- changelog/update-7900-payout-notice | 4 + .../deposits-overview/deposit-notices.tsx | 17 ---- .../deposits-overview/deposit-schedule.tsx | 84 ++++--------------- client/components/deposits-overview/index.tsx | 6 -- .../test/__snapshots__/index.tsx.snap | 45 +--------- 5 files changed, 23 insertions(+), 133 deletions(-) create mode 100644 changelog/update-7900-payout-notice diff --git a/changelog/update-7900-payout-notice b/changelog/update-7900-payout-notice new file mode 100644 index 00000000000..4a49df73e41 --- /dev/null +++ b/changelog/update-7900-payout-notice @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Remove payout timing notice and update the help tooltil on Payments Overview page. diff --git a/client/components/deposits-overview/deposit-notices.tsx b/client/components/deposits-overview/deposit-notices.tsx index 281785fc60a..7a65a9a8e6f 100644 --- a/client/components/deposits-overview/deposit-notices.tsx +++ b/client/components/deposits-overview/deposit-notices.tsx @@ -5,7 +5,6 @@ import React from 'react'; import { __, sprintf } from '@wordpress/i18n'; import interpolateComponents from '@automattic/interpolate-components'; import { Link } from '@woocommerce/components'; -import { tip } from '@wordpress/icons'; import { ExternalLink } from '@wordpress/components'; import { addQueryArgs } from '@wordpress/url'; @@ -104,22 +103,6 @@ export const NewAccountWaitingPeriodNotice: React.FC = () => ( ); -/** - * Renders a notice informing the user of the number of days it may take for deposits to appear in their bank account. - */ -export const DepositTransitDaysNotice: React.FC = () => ( - - { __( - 'It may take 1-3 business days for payouts to reach your bank account.', - 'woocommerce-payments' - ) } - -); - /** * Renders a notice informing the user that their deposits may be paused due to a negative balance. */ diff --git a/client/components/deposits-overview/deposit-schedule.tsx b/client/components/deposits-overview/deposit-schedule.tsx index c463dfa335f..11221606544 100644 --- a/client/components/deposits-overview/deposit-schedule.tsx +++ b/client/components/deposits-overview/deposit-schedule.tsx @@ -118,72 +118,24 @@ const DepositSchedule: React.FC< DepositScheduleProps > = ( { const nextDepositHelpContent = ( <> - { __( - 'Payouts are initiated based on the following criteria:', - 'woocommerce-payments' - ) } - + { interpolateComponents( { + mixedString: __( + 'The timing and amount of your payouts may vary due to several factors. Check out our {{link}}payout schedule guide{{/link}} for details.', + 'woocommerce-payments' + ), + components: { + link: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + }, + } ) } ); diff --git a/client/components/deposits-overview/index.tsx b/client/components/deposits-overview/index.tsx index 11c0b3aa023..c00eca0f953 100644 --- a/client/components/deposits-overview/index.tsx +++ b/client/components/deposits-overview/index.tsx @@ -24,7 +24,6 @@ import RecentDepositsList from './recent-deposits-list'; import DepositSchedule from './deposit-schedule'; import { DepositMinimumBalanceNotice, - DepositTransitDaysNotice, NegativeBalanceDepositsPausedNotice, NewAccountWaitingPeriodNotice, NoFundsAvailableForDepositNotice, @@ -149,11 +148,6 @@ const DepositsOverview: React.FC = () => { ) : ( <> - { isDepositsUnrestricted && - ! isDepositAwaitingPendingFunds && - ! hasErroredExternalAccount && ( - - ) } { ! hasCompletedWaitingPeriod && ( ) } diff --git a/client/components/deposits-overview/test/__snapshots__/index.tsx.snap b/client/components/deposits-overview/test/__snapshots__/index.tsx.snap index ac1ac4c69ca..f80d3f68edf 100644 --- a/client/components/deposits-overview/test/__snapshots__/index.tsx.snap +++ b/client/components/deposits-overview/test/__snapshots__/index.tsx.snap @@ -138,50 +138,7 @@ exports[`Deposits Overview information Component Renders 1`] = ` class="components-card__body components-card-body wcpay-deposits-overview__notices__container css-1nwhnu3-View-Body-borderRadius-medium em57xhy0" data-wp-c16t="true" data-wp-component="CardBody" - > -
-
-
-
- -
-
- It may take 1-3 business days for payouts to reach your bank account. -
-
-
-
-
-
+ />
Date: Tue, 3 Dec 2024 15:45:48 +0800 Subject: [PATCH 06/83] [Mobile] Display 'In-Person (POS)' for transactions from mobile POS (#9802) --- changelog/mobile-tpv-tracking-channel | 4 ++ client/data/transactions/hooks.ts | 2 +- client/payment-details/summary/index.tsx | 3 +- client/transactions/list/index.tsx | 6 +-- client/utils/charge/index.ts | 50 +++++++++++++++------ client/utils/charge/test/index.js | 56 ++++++++++++++++++++++++ 6 files changed, 103 insertions(+), 18 deletions(-) create mode 100644 changelog/mobile-tpv-tracking-channel diff --git a/changelog/mobile-tpv-tracking-channel b/changelog/mobile-tpv-tracking-channel new file mode 100644 index 00000000000..a7b990214df --- /dev/null +++ b/changelog/mobile-tpv-tracking-channel @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. diff --git a/client/data/transactions/hooks.ts b/client/data/transactions/hooks.ts index d0a983e75f9..e65ba57a03f 100644 --- a/client/data/transactions/hooks.ts +++ b/client/data/transactions/hooks.ts @@ -32,7 +32,7 @@ export interface Transaction { transaction_id: string; date: string; type: 'charge' | 'refund' | 'financing_payout' | 'financing_paydown'; - channel: 'in_person' | 'online'; + channel: 'in_person' | 'in_person_pos' | 'online'; // A field to identify the payment's source. // Usually last 4 digits for card payments, bank name for bank transfers... source_identifier: string; diff --git a/client/payment-details/summary/index.tsx b/client/payment-details/summary/index.tsx index 06699f07147..1e5b6bfcac9 100644 --- a/client/payment-details/summary/index.tsx +++ b/client/payment-details/summary/index.tsx @@ -123,7 +123,8 @@ const composePaymentSummaryItems = ( { { isTapToPay( metadata?.reader_model ) ? getTapToPayChannel( metadata?.platform ) : getChargeChannel( - charge.payment_method_details?.type + charge.payment_method_details?.type, + metadata ) } ), diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index b2c1cb4c9a7..a8c437d6d2c 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -55,7 +55,7 @@ import { formatExplicitCurrency, formatExportAmount, } from 'multi-currency/interface/functions'; -import { getChargeChannel } from 'utils/charge'; +import { getTransactionChannel } from 'utils/charge'; import Deposit from './deposit'; import ConvertedAmount from './converted-amount'; import autocompleter from 'transactions/autocompleter'; @@ -473,10 +473,10 @@ export const TransactionsList = ( ), }, channel: { - value: getChargeChannel( txn.channel ), + value: getTransactionChannel( txn.channel ), display: clickable( - { getChargeChannel( txn.channel ) } + { getTransactionChannel( txn.channel ) } { txn.source_device && getSourceDeviceIcon( txn ) } ), diff --git a/client/utils/charge/index.ts b/client/utils/charge/index.ts index 2690279e2cb..0169a45b4e0 100755 --- a/client/utils/charge/index.ts +++ b/client/utils/charge/index.ts @@ -173,24 +173,48 @@ export const getChargeAmounts = ( charge: Charge ): ChargeAmounts => { }; /** - * Displays the transaction's channel: Online | In-Person. + * Displays the transaction's channel: Online | In-Person | In-Person (POS). + * This method is called on the list of transactions page. * - * This method is called in two places: The individual transaction page, and the list of transactions page. - * In the individual transaction page, we are getting the data from Stripe, so we pass the transaction.type - * which can be card_present or interac_present for In-Person payments. * In the list of transactions, the type holds the brand of the payment method, so we aren't passing it. - * Instead, we pass the transaction.channel directly, which might be in_person|online. + * Instead, we pass the transaction.channel directly, which might be in_person|in_person_pos|online. * - * @param {string} type The transaction type. - * @return {string} Online or In-Person. + * @param {string} channel The transaction channel. + * @return {string} Online, In-Person, or In-Person (POS). + */ +export const getTransactionChannel = ( channel: string ): string => { + switch ( channel ) { + case 'in_person': + return __( 'In-Person', 'woocommerce-payments' ); + case 'in_person_pos': + return __( 'In-Person (POS)', 'woocommerce-payments' ); + default: + return __( 'Online', 'woocommerce-payments' ); + } +}; + +/** + * Displays the channel based on the charge data from Stripe and metadata for a transaction: Online | In-Person | In-Person (POS). + * This method is called in the individual transaction page. + * + * In the individual transaction page, we are getting the data from Stripe, so we pass the charge.type + * which can be card_present or interac_present for In-Person payments. In addition, we pass the transaction metadata + * whose ipp_channel value can be mobile_store_management or mobile_pos that indicates whether the channel is from store + * management or POS in the mobile apps. + * + * @param {string} type The transaction charge type, which can be card_present or interac_present for In-Person payments. + * @param {Record} metadata The transaction metadata, which may include ipp_channel indicating the channel source. + * @return {string} Returns 'Online', 'In-Person', or 'In-Person (POS)' based on the transaction type and metadata. * */ -export const getChargeChannel = ( type: string ): string => { - if ( - type === 'card_present' || - type === 'interac_present' || - type === 'in_person' - ) { +export const getChargeChannel = ( + type: string, + metadata: Record< string, any > +): string => { + if ( type === 'card_present' || type === 'interac_present' ) { + if ( metadata?.ipp_channel === 'mobile_pos' ) { + return __( 'In-Person (POS)', 'woocommerce-payments' ); + } return __( 'In-Person', 'woocommerce-payments' ); } diff --git a/client/utils/charge/test/index.js b/client/utils/charge/test/index.js index 99bc3172f07..07b7f64f312 100755 --- a/client/utils/charge/test/index.js +++ b/client/utils/charge/test/index.js @@ -482,3 +482,59 @@ describe( 'Charge utilities / getChargeAmounts', () => { } ); } ); } ); + +jest.mock( '@wordpress/i18n', () => ( { + __: jest.fn( ( text ) => text ), +} ) ); + +describe( 'Charge utilities / get channel string', () => { + describe( 'getTransactionChannel', () => { + test( 'should return "In-Person (POS)" for in_person_pos channel', () => { + const result = utils.getTransactionChannel( 'in_person_pos' ); + expect( result ).toBe( 'In-Person (POS)' ); + } ); + + test( 'should return "In-Person" for in_person channel', () => { + const result = utils.getTransactionChannel( 'in_person' ); + expect( result ).toBe( 'In-Person' ); + } ); + + test( 'should return "Online" for online channel', () => { + const result = utils.getTransactionChannel( 'online' ); + expect( result ).toBe( 'Online' ); + } ); + + test( 'should return "Online" for null channel', () => { + const result = utils.getTransactionChannel( null ); + expect( result ).toBe( 'Online' ); + } ); + } ); + + describe( 'getChargeChannel', () => { + test( 'should return "In-Person (POS)" for card_present type with mobile_pos metadata', () => { + const result = utils.getChargeChannel( 'card_present', { + ipp_channel: 'mobile_pos', + } ); + expect( result ).toBe( 'In-Person (POS)' ); + } ); + + test( 'should return "In-Person" for card_present type with mobile_store_management metadata', () => { + const result = utils.getChargeChannel( 'card_present', { + ipp_channel: 'mobile_store_management', + } ); + expect( result ).toBe( 'In-Person' ); + } ); + + test( 'should return "In-Person" for card_present type with null ipp_channel metadata', () => { + const result = utils.getChargeChannel( 'card_present', {} ); + expect( result ).toBe( 'In-Person' ); + } ); + + test( 'should return "Online" for online type', () => { + const result = utils.getChargeChannel( 'online', { + ipp_channel: 'mobile_pos', + } ); + expect( result ).toBe( 'Online' ); + } ); + } ); +} ); From a622dac811dd460c2a2bcefb347ce8ea2b5a8395 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 3 Dec 2024 10:04:28 +0100 Subject: [PATCH 07/83] chore: PRB references to ECE (#9780) Co-authored-by: Samir Merchant --- .../chore-prb-references-in-ece-docs-and-logs | 4 ++ ...xpress-checkout-button-display-handler.php | 2 +- ...yments-express-checkout-button-handler.php | 4 +- ...ayments-express-checkout-button-helper.php | 38 +++++++++---------- 4 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 changelog/chore-prb-references-in-ece-docs-and-logs diff --git a/changelog/chore-prb-references-in-ece-docs-and-logs b/changelog/chore-prb-references-in-ece-docs-and-logs new file mode 100644 index 00000000000..887525ff7bc --- /dev/null +++ b/changelog/chore-prb-references-in-ece-docs-and-logs @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php index 1f9470bbf65..f9c978d43a1 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-display-handler.php @@ -123,7 +123,7 @@ public function display_express_checkout_buttons() { $should_show_woopay = $this->platform_checkout_button_handler->should_show_woopay_button(); $should_show_express_checkout_button = $this->express_checkout_helper->should_show_express_checkout_button(); - // When Payment Request button is enabled, we need the separator markup on the page, but hidden in case the browser doesn't have any payment request methods to display. + // When Express Checkout button is enabled, we need the separator markup on the page, but hidden in case the browser doesn't have any express payment methods to display. // More details: https://github.com/Automattic/woocommerce-payments/pull/5399#discussion_r1073633776. $separator_starts_hidden = ! $should_show_woopay; if ( $should_show_woopay || $should_show_express_checkout_button ) { diff --git a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php index 03f0100cefa..f580cc45565 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-button-handler.php @@ -204,7 +204,7 @@ public function is_account_creation_possible() { $is_signup_from_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_from_checkout_for_subscriptions', 'no' ); } - // If automatically generate username/password are disabled, the Payment Request API + // If automatically generate username/password are disabled, the Express Checkout API // can't include any of those fields, so account creation is not possible. return ( $is_signup_from_checkout_allowed && @@ -311,7 +311,7 @@ public function scripts() { } /** - * Display the payment request button. + * Display the express checkout button. */ public function display_express_checkout_button_html() { if ( ! $this->express_checkout_helper->should_show_express_checkout_button() ) { 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 561679882a7..285ce659d94 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 @@ -59,7 +59,7 @@ public function get_booking_id_from_cart() { } /** - * Builds the line items to pass to Payment Request + * Builds the line items to pass to Express Checkout * * @param boolean $itemized_display_items Indicates whether to show subtotals or itemized views. */ @@ -182,7 +182,7 @@ public function get_total_label() { * @return int */ public function get_quantity() { - // Payment Request Button sends the quantity as qty. WooPay sends it as quantity. + // Express Checkout Element sends the quantity as qty. WooPay sends it as quantity. if ( isset( $_POST['quantity'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing return absint( $_POST['quantity'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing } elseif ( isset( $_POST['qty'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing @@ -245,7 +245,7 @@ public function is_available_at( $location, $option_name ) { } /** - * Gets settings that are shared between the Payment Request button and the WooPay button. + * Gets settings that are shared between the Express Checkout button and the WooPay button. * * @return array */ @@ -361,7 +361,7 @@ public function is_product_subscription( WC_Product $product ): bool { } /** - * Checks whether Payment Request Button should be available on this page. + * Checks whether Express Checkout Element Button should be available on this page. * * @return bool */ @@ -373,7 +373,7 @@ public function should_show_express_checkout_button() { // If no SSL, bail. if ( ! WC_Payments::mode()->is_test() && ! is_ssl() ) { - Logger::log( 'Stripe Payment Request live mode requires SSL.' ); + Logger::log( 'Stripe Express Checkout live mode requires SSL.' ); return false; } @@ -400,13 +400,13 @@ public function should_show_express_checkout_button() { // Product page, but has unsupported product type. if ( $this->is_product() && ! $this->is_product_supported() ) { - Logger::log( 'Product page has unsupported product type ( Payment Request button disabled )' ); + Logger::log( 'Product page has unsupported product type ( Express Checkout Element button disabled )' ); return false; } // Cart has unsupported product type. if ( ( $this->is_checkout() || $this->is_cart() ) && ! $this->has_allowed_items_in_cart() ) { - Logger::log( 'Items in the cart have unsupported product type ( Payment Request button disabled )' ); + Logger::log( 'Items in the cart have unsupported product type ( Express Checkout Element button disabled )' ); return false; } @@ -439,7 +439,7 @@ public function should_show_express_checkout_button() { ( $this->is_product() && 0.0 === (float) $this->get_product()->get_price() ) ) { - Logger::log( 'Order price is 0 ( Payment Request button disabled )' ); + Logger::log( 'Order price is 0 ( Express Checkout Element button disabled )' ); return false; } @@ -509,11 +509,11 @@ public function has_allowed_items_in_cart() { } /** - * Filter whether product supports Payment Request Button on cart page. + * Filter whether product supports Express Checkout Element Button on cart page. * * @since 6.9.0 * - * @param boolean $is_supported Whether product supports Payment Request Button on cart page. + * @param boolean $is_supported Whether product supports Express Checkout Element Button on cart page. * @param object $_product Product object. */ if ( ! apply_filters( 'wcpay_payment_request_is_cart_supported', true, $_product ) ) { @@ -530,7 +530,7 @@ public function has_allowed_items_in_cart() { } } - // We don't support multiple packages with Payment Request Buttons because we can't offer a good UX. + // We don't support multiple packages with Express Checkout Element Buttons because we can't offer a good UX. $packages = WC()->cart->get_shipping_packages(); if ( 1 < ( is_countable( $packages ) ? count( $packages ) : 0 ) ) { return false; @@ -618,7 +618,7 @@ public function get_shipping_options( $shipping_address, $itemized_display_items /** * Restores the shipping methods previously chosen for each recurring cart after shipping was reset and recalculated - * during the Payment Request get_shipping_options flow. + * during the Express Checkout get_shipping_options flow. * * When the cart contains multiple subscriptions with different billing periods, customers are able to select different shipping * methods for each subscription, however, this is not supported when purchasing with Apple Pay and Google Pay as it's @@ -888,7 +888,7 @@ public function get_normalized_state( $state, $country ) { return $state; } - // Try to match state from the Payment Request API list of states. + // Try to match state from the Express Checkout API list of states. $state = $this->get_normalized_state_from_ece_states( $state, $country ); // If it's normalized, return. @@ -902,11 +902,11 @@ public function get_normalized_state( $state, $country ) { } /** - * The Payment Request API provides its own validation for the address form. + * The Express Checkout Element API provides its own validation for the address form. * For some countries, it might not provide a state field, so we need to return a more descriptive - * error message, indicating that the Payment Request button is not supported for that country. + * error message, indicating that the Express Checkout Element button is not supported for that country. */ - public static function validate_state() { + public function validate_state() { $wc_checkout = WC_Checkout::instance(); $posted_data = $wc_checkout->get_posted_data(); $checkout_fields = $wc_checkout->get_checkout_fields(); @@ -927,7 +927,7 @@ public static function validate_state() { wc_add_notice( sprintf( /* translators: %s: country. */ - __( 'The payment request button is not supported in %s because some required fields couldn\'t be verified. Please proceed to the checkout page and try again.', 'woocommerce-payments' ), + __( 'The express checkout is not supported in %s because some required fields couldn\'t be verified. Please proceed to the checkout page and try again.', 'woocommerce-payments' ), $countries[ $posted_data['billing_country'] ] ?? $posted_data['billing_country'] ), 'error' @@ -969,7 +969,7 @@ public function is_normalized_state( $state, $country ) { } /** - * Get normalized state from Payment Request API dropdown list of states. + * Get normalized state from Express Checkout API dropdown list of states. * * @param string $state Full state name or state code. * @param string $country Two-letter country code. @@ -987,7 +987,7 @@ public function get_normalized_state_from_ece_states( $state, $country ) { foreach ( $pr_states[ $country ] as $wc_state_abbr => $pr_state ) { $sanitized_state_string = $this->sanitize_string( $state ); - // Checks if input state matches with Payment Request state code (0), name (1) or localName (2). + // Checks if input state matches with Express Checkout state code (0), name (1) or localName (2). if ( ( ! empty( $pr_state[0] ) && $sanitized_state_string === $this->sanitize_string( $pr_state[0] ) ) || ( ! empty( $pr_state[1] ) && $sanitized_state_string === $this->sanitize_string( $pr_state[1] ) ) || From 6cff322e9167634f63641151b0e992904bccde96 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Tue, 3 Dec 2024 11:48:48 -0300 Subject: [PATCH 08/83] Fixed Affirm using black logo on dark themes (#9805) --- changelog/fix-upe-theme-block | 4 ++++ client/checkout/blocks/index.js | 2 -- client/checkout/blocks/payment-method-label.js | 14 +++++--------- 3 files changed, 9 insertions(+), 11 deletions(-) create mode 100644 changelog/fix-upe-theme-block diff --git a/changelog/fix-upe-theme-block b/changelog/fix-upe-theme-block new file mode 100644 index 00000000000..6afa59f04d3 --- /dev/null +++ b/changelog/fix-upe-theme-block @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed Affirm using black logo on dark themes diff --git a/client/checkout/blocks/index.js b/client/checkout/blocks/index.js index cdb3d105861..3f98863d707 100644 --- a/client/checkout/blocks/index.js +++ b/client/checkout/blocks/index.js @@ -64,7 +64,6 @@ const upeMethods = { }; const enabledPaymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); -const upeAppearanceTheme = getUPEConfig( 'wcBlocksUPEAppearanceTheme' ); const isStripeLinkEnabled = isLinkEnabled( enabledPaymentMethodsConfig ); // Create an API object, which will be used throughout the checkout. @@ -116,7 +115,6 @@ Object.entries( enabledPaymentMethodsConfig ) iconLight={ upeConfig.icon } iconDark={ upeConfig.darkIcon } upeName={ upeName } - upeAppearanceTheme={ upeAppearanceTheme } /> ), ariaLabel: 'WooPayments', diff --git a/client/checkout/blocks/payment-method-label.js b/client/checkout/blocks/payment-method-label.js index 7a18cef4bcd..752a9b830db 100644 --- a/client/checkout/blocks/payment-method-label.js +++ b/client/checkout/blocks/payment-method-label.js @@ -47,20 +47,15 @@ const PaymentMethodMessageWrapper = ( { ); }; -export default ( { - api, - title, - countries, - iconLight, - iconDark, - upeName, - upeAppearanceTheme, -} ) => { +export default ( { api, title, countries, iconLight, iconDark, upeName } ) => { const cartData = wp.data.select( 'wc/store/cart' ).getCartData(); const isTestMode = getUPEConfig( 'testMode' ); const [ appearance, setAppearance ] = useState( getUPEConfig( 'wcBlocksUPEAppearance' ) ); + const [ upeAppearanceTheme, setUpeAppearanceTheme ] = useState( + getUPEConfig( 'wcBlocksUPEAppearanceTheme' ) + ); // Stripe expects the amount to be sent as the minor unit of 2 digits. const amount = parseInt( @@ -86,6 +81,7 @@ export default ( { 'blocks_checkout' ); setAppearance( upeAppearance ); + setUpeAppearanceTheme( upeAppearance.theme ); } if ( ! appearance ) { From af82606b040ce2d38cde7c2ae2ae00b09749e9a2 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Wed, 4 Dec 2024 09:22:22 -0300 Subject: [PATCH 09/83] Make test instructions copy icon use the same color as the text next to it (#9868) --- changelog/test-instructions-item-color | 4 ++++ client/checkout/style.scss | 21 +++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 changelog/test-instructions-item-color diff --git a/changelog/test-instructions-item-color b/changelog/test-instructions-item-color new file mode 100644 index 00000000000..4bf5983e8e6 --- /dev/null +++ b/changelog/test-instructions-item-color @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Make test instructions copy icon use the same color as the text next to it diff --git a/client/checkout/style.scss b/client/checkout/style.scss index 2c365b2fc14..abf8c18f543 100644 --- a/client/checkout/style.scss +++ b/client/checkout/style.scss @@ -26,16 +26,19 @@ display: block; width: 1.2em; height: 1.2em; - background: url( 'assets/images/icons/copy.svg?asset' ) no-repeat center; - background-size: contain; + mask-image: url( 'assets/images/icons/copy.svg?asset' ); + mask-size: contain; + mask-repeat: no-repeat; + mask-position: center; + background-color: currentColor; } &:hover { background-color: transparent; - filter: invert( 0.3 ); + opacity: 0.7; i { - filter: invert( 0.3 ); + opacity: 0.7; } } @@ -43,15 +46,13 @@ transform: scale( 0.9 ); } - &.state--success { - i { - background-image: url( 'assets/images/icons/check-green.svg?asset' ); - } + &:focus { + outline: none; } - .theme--night & { + &.state--success { i { - filter: invert( 100% ) hue-rotate( 180deg ); + mask-image: url( 'assets/images/icons/check-green.svg?asset' ); } } } From 916971729dd36207ed61dc948f21b40e5b535fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Costa?= <10233985+cesarcosta99@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:04:30 -0300 Subject: [PATCH 10/83] Ensure WooPay eligibility is taken into account when retrieving WooPay enable state in the settings (#9841) --- .../fix-9787-woopay-enable-state-settings | 4 ++ ...s-wc-rest-payments-settings-controller.php | 2 +- ...s-wc-rest-payments-settings-controller.php | 38 +++++++++++++++++-- 3 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 changelog/fix-9787-woopay-enable-state-settings diff --git a/changelog/fix-9787-woopay-enable-state-settings b/changelog/fix-9787-woopay-enable-state-settings new file mode 100644 index 00000000000..cee183680df --- /dev/null +++ b/changelog/fix-9787-woopay-enable-state-settings @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Consider WooPay eligibility when retrieving WooPay enable state in the settings. diff --git a/includes/admin/class-wc-rest-payments-settings-controller.php b/includes/admin/class-wc-rest-payments-settings-controller.php index 012604733b9..dfd6e76f005 100644 --- a/includes/admin/class-wc-rest-payments-settings-controller.php +++ b/includes/admin/class-wc-rest-payments-settings-controller.php @@ -513,7 +513,7 @@ public function get_settings(): WP_REST_Response { 'payment_request_button_border_radius' => $this->wcpay_gateway->get_option( 'payment_request_button_border_radius', WC_Payments_Express_Checkout_Button_Handler::DEFAULT_BORDER_RADIUS_IN_PX ), 'is_saved_cards_enabled' => $this->wcpay_gateway->is_saved_cards_enabled(), 'is_card_present_eligible' => $this->wcpay_gateway->is_card_present_eligible() && isset( WC()->payment_gateways()->get_available_payment_gateways()['cod'] ), - 'is_woopay_enabled' => 'yes' === $this->wcpay_gateway->get_option( 'platform_checkout' ), + 'is_woopay_enabled' => WC_Payments_Features::is_woopay_eligible() && 'yes' === $this->wcpay_gateway->get_option( 'platform_checkout' ), 'show_woopay_incompatibility_notice' => get_option( 'woopay_invalid_extension_found', false ), 'woopay_custom_message' => $this->wcpay_gateway->get_option( 'platform_checkout_custom_message' ), 'woopay_store_logo' => $this->wcpay_gateway->get_option( 'platform_checkout_store_logo' ), diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index d68c5c1f82e..459a6a7bf08 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -73,7 +73,7 @@ class WC_REST_Payments_Settings_Controller_Test extends WCPAY_UnitTestCase { /** * @var Database_Cache|MockObject */ - private $mock_db_cache; + private $mock_cache; /** * WC_Payments_Localization_Service instance. @@ -117,15 +117,19 @@ public function set_up() { // Set the user so that we can pass the authentication. wp_set_current_user( 1 ); + // Mock the main class's cache service. + $this->_cache = WC_Payments::get_database_cache(); + $this->mock_cache = $this->createMock( Database_Cache::class ); + WC_Payments::set_database_cache( $this->mock_cache ); + $this->mock_api_client = $this->getMockBuilder( WC_Payments_API_Client::class ) ->disableOriginalConstructor() ->getMock(); $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); - $this->mock_db_cache = $this->createMock( Database_Cache::class ); $this->mock_session_service = $this->createMock( WC_Payments_Session_Service::class ); $order_service = new WC_Payments_Order_Service( $this->mock_api_client ); - $customer_service = new WC_Payments_Customer_Service( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_db_cache, $this->mock_session_service, $order_service ); + $customer_service = new WC_Payments_Customer_Service( $this->mock_api_client, $this->mock_wcpay_account, $this->mock_cache, $this->mock_session_service, $order_service ); $token_service = new WC_Payments_Token_Service( $this->mock_api_client, $customer_service ); $compatibility_service = new Compatibility_Service( $this->mock_api_client ); $action_scheduler_service = new WC_Payments_Action_Scheduler_Service( $this->mock_api_client, $order_service, $compatibility_service ); @@ -205,6 +209,8 @@ public function set_up() { public function tear_down() { parent::tear_down(); WC_Blocks_REST_API_Registration_Preventer::stop_preventing(); + // Restore the cache service in the main class. + WC_Payments::set_database_cache( $this->_cache ); } public function test_get_settings_request_returns_status_code_200() { @@ -745,6 +751,32 @@ public function test_get_settings_domestic_currency_fallbacks_to_default_currenc $this->assertSame( $this->domestic_currency, $response->get_data()['account_domestic_currency'] ); } + public function test_get_settings_is_woopay_enabled_returns_true(): void { + $current_platform_checkout = $this->gateway->get_option( 'platform_checkout' ); + + $this->gateway->update_option( 'platform_checkout', 'yes' ); + $this->mock_cache->method( 'get' )->willReturn( [ 'platform_checkout_eligible' => true ] ); + + $response = $this->controller->get_settings(); + + $this->assertArrayHasKey( 'is_woopay_enabled', $response->get_data() ); + $this->assertTrue( $response->get_data()['is_woopay_enabled'] ); + $this->gateway->update_option( 'platform_checkout', $current_platform_checkout ); + } + + public function test_get_settings_is_woopay_enabled_returns_false_if_it_is_not_eligible(): void { + $current_platform_checkout = $this->gateway->get_option( 'platform_checkout' ); + + $this->gateway->update_option( 'platform_checkout', 'yes' ); + $this->mock_cache->method( 'get' )->willReturn( [ 'platform_checkout_eligible' => false ] ); + + $response = $this->controller->get_settings(); + + $this->assertArrayHasKey( 'is_woopay_enabled', $response->get_data() ); + $this->assertFalse( $response->get_data()['is_woopay_enabled'] ); + $this->gateway->update_option( 'platform_checkout', $current_platform_checkout ); + } + /** * Tests account business support address validator * From 0d800074f0fa7c8a4bf7de474dad80ffb441df2c Mon Sep 17 00:00:00 2001 From: Malith Senaweera <6216000+malithsen@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:57:40 -0600 Subject: [PATCH 11/83] Update WooPay theme checkbox copy in settings page (#9843) --- .../fix-change-woopay-theming-settings-copy | 4 ++++ .../woopay-settings.js | 22 +++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-change-woopay-theming-settings-copy diff --git a/changelog/fix-change-woopay-theming-settings-copy b/changelog/fix-change-woopay-theming-settings-copy new file mode 100644 index 00000000000..fa73b3672f8 --- /dev/null +++ b/changelog/fix-change-woopay-theming-settings-copy @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +WooPay theming copy in the settings page diff --git a/client/settings/express-checkout-settings/woopay-settings.js b/client/settings/express-checkout-settings/woopay-settings.js index aa73506dde1..0ba3f8f7b92 100644 --- a/client/settings/express-checkout-settings/woopay-settings.js +++ b/client/settings/express-checkout-settings/woopay-settings.js @@ -213,7 +213,7 @@ const WooPaySettings = ( { section } ) => {
From 1fc7a8ac8d74d2ea7f5b87e5cb6e150324fb4233 Mon Sep 17 00:00:00 2001 From: Malith Senaweera <6216000+malithsen@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:58:20 -0600 Subject: [PATCH 12/83] Limit WooPay theme-ing availability to shortcode entrypoint (#9867) --- ...imit-woopay-themeing-to-shortcode-checkout | 5 +++ client/checkout/woopay/email-input-iframe.js | 10 ++++-- .../express-button/express-checkout-iframe.js | 10 ++++-- .../woopay-express-checkout-button.test.js | 35 ++----------------- .../woopay-express-checkout-button.js | 16 ++++----- client/checkout/woopay/utils.js | 10 ++++++ 6 files changed, 39 insertions(+), 47 deletions(-) create mode 100644 changelog/add-limit-woopay-themeing-to-shortcode-checkout diff --git a/changelog/add-limit-woopay-themeing-to-shortcode-checkout b/changelog/add-limit-woopay-themeing-to-shortcode-checkout new file mode 100644 index 00000000000..4c089593b1f --- /dev/null +++ b/changelog/add-limit-woopay-themeing-to-shortcode-checkout @@ -0,0 +1,5 @@ +Significance: patch +Type: add +Comment: Updates the availability criteria of WooPay Global theme-ing feature. This feature is unreleased and behind a feature flag. + + diff --git a/client/checkout/woopay/email-input-iframe.js b/client/checkout/woopay/email-input-iframe.js index c5ccaceed96..1ecd86ca031 100644 --- a/client/checkout/woopay/email-input-iframe.js +++ b/client/checkout/woopay/email-input-iframe.js @@ -13,6 +13,7 @@ import { appendRedirectionParams, shouldSkipWooPay, deleteSkipWooPayCookie, + isSupportedThemeEntrypoint, } from './utils'; import { getAppearanceType } from '../utils'; @@ -180,6 +181,11 @@ export const handleWooPayEmailInput = async ( // Set the initial value. iframeHeaderValue = true; const appearanceType = getAppearanceType(); + const appearance = + isSupportedThemeEntrypoint( appearanceType ) && + getConfig( 'isWooPayGlobalThemeSupportEnabled' ) + ? getAppearance( appearanceType, true ) + : null; if ( getConfig( 'isWoopayFirstPartyAuthEnabled' ) ) { request( @@ -189,9 +195,7 @@ export const handleWooPayEmailInput = async ( order_id: getConfig( 'order_id' ), key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), - appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType, true ) - : null, + appearance: appearance, } ).then( ( response ) => { if ( response?.data?.session ) { diff --git a/client/checkout/woopay/express-button/express-checkout-iframe.js b/client/checkout/woopay/express-button/express-checkout-iframe.js index 19d4ff54fe7..296b40aed17 100644 --- a/client/checkout/woopay/express-button/express-checkout-iframe.js +++ b/client/checkout/woopay/express-button/express-checkout-iframe.js @@ -14,6 +14,7 @@ import { getTargetElement, validateEmail, appendRedirectionParams, + isSupportedThemeEntrypoint, } from '../utils'; import { getTracksIdentity } from 'tracks'; import { getAppearance } from 'wcpay/checkout/upe-styles'; @@ -100,6 +101,11 @@ export const expressCheckoutIframe = async ( api, context, emailSelector ) => { // Set the initial value. iframeHeaderValue = true; const appearanceType = getAppearanceType(); + const appearance = + isSupportedThemeEntrypoint( appearanceType ) && + getConfig( 'isWooPayGlobalThemeSupportEnabled' ) + ? getAppearance( appearanceType, true ) + : null; if ( getConfig( 'isWoopayFirstPartyAuthEnabled' ) ) { request( @@ -109,9 +115,7 @@ export const expressCheckoutIframe = async ( api, context, emailSelector ) => { order_id: getConfig( 'order_id' ), key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), - appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType, true ) - : null, + appearance: appearance, } ).then( ( response ) => { if ( response?.data?.session ) { diff --git a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js index 47bf07d77fe..72e2783d3ac 100644 --- a/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js +++ b/client/checkout/woopay/express-button/test/woopay-express-checkout-button.test.js @@ -70,37 +70,6 @@ describe( 'WoopayExpressCheckoutButton', () => { const mockRequest = jest.fn().mockResolvedValue( true ); const mockAddToCart = jest.fn().mockResolvedValue( true ); const api = new WCPayAPI( {}, mockRequest ); - const mockAppearance = { - rules: { - '.Block': {}, - '.Input': {}, - '.Input--invalid': {}, - '.Label': {}, - '.Tab': {}, - '.Tab--selected': {}, - '.Tab:hover': {}, - '.TabIcon--selected': { - color: undefined, - }, - '.TabIcon:hover': { - color: undefined, - }, - '.Text': {}, - '.Text--redirect': {}, - '.Heading': {}, - '.Button': {}, - '.Container': {}, - '.Link': {}, - }, - theme: 'stripe', - variables: { - colorBackground: '#ffffff', - colorText: undefined, - fontFamily: undefined, - fontSizeBase: undefined, - }, - labels: 'above', - }; beforeEach( () => { expressCheckoutIframe.mockImplementation( () => jest.fn() ); @@ -198,7 +167,7 @@ describe( 'WoopayExpressCheckoutButton', () => { case 'order_id': return 1; case 'appearance': - return mockAppearance; + return null; default: return 'foo'; } @@ -224,7 +193,7 @@ describe( 'WoopayExpressCheckoutButton', () => { order_id: 1, key: 'testkey', billing_email: 'test@test.com', - appearance: mockAppearance, + appearance: null, } ); expect( expressCheckoutIframe ).not.toHaveBeenCalled(); } ); diff --git a/client/checkout/woopay/express-button/woopay-express-checkout-button.js b/client/checkout/woopay/express-button/woopay-express-checkout-button.js index 844ff25f010..5362542eed3 100644 --- a/client/checkout/woopay/express-button/woopay-express-checkout-button.js +++ b/client/checkout/woopay/express-button/woopay-express-checkout-button.js @@ -19,6 +19,7 @@ import interpolateComponents from '@automattic/interpolate-components'; import { appendRedirectionParams, deleteSkipWooPayCookie, + isSupportedThemeEntrypoint, } from 'wcpay/checkout/woopay/utils'; import WooPayFirstPartyAuth from 'wcpay/checkout/woopay/express-button/woopay-first-party-auth'; import { getAppearance } from 'wcpay/checkout/upe-styles'; @@ -221,6 +222,11 @@ export const WoopayExpressCheckoutButton = ( { setIsLoading( true ); const appearanceType = getAppearanceType(); + const appearance = + isSupportedThemeEntrypoint( appearanceType ) && + getConfig( 'isWooPayGlobalThemeSupportEnabled' ) + ? getAppearance( appearanceType, true ) + : null; if ( isProductPage ) { const productData = getProductDataRef.current(); @@ -242,11 +248,7 @@ export const WoopayExpressCheckoutButton = ( { } WooPayFirstPartyAuth.getWooPaySessionFromMerchant( { _ajax_nonce: getConfig( 'woopaySessionNonce' ), - appearance: getConfig( - 'isWooPayGlobalThemeSupportEnabled' - ) - ? getAppearance( appearanceType, true ) - : null, + appearance: appearance, } ) .then( async ( response ) => { if ( @@ -290,9 +292,7 @@ export const WoopayExpressCheckoutButton = ( { order_id: getConfig( 'order_id' ), key: getConfig( 'key' ), billing_email: getConfig( 'billing_email' ), - appearance: getConfig( 'isWooPayGlobalThemeSupportEnabled' ) - ? getAppearance( appearanceType, true ) - : null, + appearance: appearance, } ) .then( async ( response ) => { if ( response?.blog_id && response?.data?.session ) { diff --git a/client/checkout/woopay/utils.js b/client/checkout/woopay/utils.js index f86d3973a7a..2c1d890b478 100644 --- a/client/checkout/woopay/utils.js +++ b/client/checkout/woopay/utils.js @@ -94,3 +94,13 @@ export const deleteSkipWooPayCookie = () => { document.cookie = 'skip_woopay=; path=/; expires=Thu, 01 Jan 1970 00:00:00 UTC;'; }; + +/** + * Determine Global theming availability for the entrypoint based on the appearanceType. + * + * @param {string} appearanceType entrypoint identifier. + * @return {boolean} True if Global theming should be enabled for the entrypoint. + */ +export const isSupportedThemeEntrypoint = ( appearanceType ) => { + return appearanceType === 'woopay_shortcode_checkout'; +}; From b1f986182cfeb8a1249405cd53b9d368b444407d Mon Sep 17 00:00:00 2001 From: Jessy Pappachan <32092402+jessy-p@users.noreply.github.com> Date: Thu, 5 Dec 2024 11:31:37 +0530 Subject: [PATCH 13/83] Use `type_is_in` filter from the client to filter by multiple transaction types. (#9871) Co-authored-by: Jessy Co-authored-by: Nagesh Pai <4162931+nagpai@users.noreply.github.com> --- changelog/fix-use-type-is-in-filter | 4 ++++ .../payment-activity-data.tsx | 24 +++++++++---------- .../test/__snapshots__/index.test.tsx.snap | 8 +++---- client/data/transactions/hooks.ts | 6 +++++ client/data/transactions/resolvers.js | 1 + client/transactions/declarations.d.ts | 1 + client/transactions/filters/config.ts | 1 + .../request/class-list-transactions.php | 1 + 8 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 changelog/fix-use-type-is-in-filter diff --git a/changelog/fix-use-type-is-in-filter b/changelog/fix-use-type-is-in-filter new file mode 100644 index 00000000000..3639b203c36 --- /dev/null +++ b/changelog/fix-use-type-is-in-filter @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. diff --git a/client/components/payment-activity/payment-activity-data.tsx b/client/components/payment-activity/payment-activity-data.tsx index 255770e587f..7731ecfb5b1 100644 --- a/client/components/payment-activity/payment-activity-data.tsx +++ b/client/components/payment-activity/payment-activity-data.tsx @@ -17,7 +17,7 @@ import { getAdminUrl } from 'wcpay/utils'; import type { PaymentActivityData } from 'wcpay/data/payment-activity/types'; import './style.scss'; -const searchTermsForViewReportLink = { +const typeFiltersForViewReportLink = { totalPaymentVolume: [ 'charge', 'payment', @@ -43,11 +43,11 @@ const searchTermsForViewReportLink = { dispute: [ 'dispute', 'dispute_reversal' ], }; -const getSearchParams = ( searchTerms: string[] ) => { - return searchTerms.reduce( +const getTypeFilters = ( types: string[] ) => { + return types.reduce( ( acc, term, index ) => ( { ...acc, - [ `search[${ index }]` ]: term, + [ `type_is_in[${ index }]` ]: term, } ), {} ); @@ -122,8 +122,8 @@ const PaymentActivityDataComponent: React.FC< Props > = ( { 'date_between[1]': moment( paymentActivityData?.date_end ) .add( siteTimeZone ) .format( 'YYYY-MM-DD' ), - ...getSearchParams( - searchTermsForViewReportLink.totalPaymentVolume + ...getTypeFilters( + typeFiltersForViewReportLink.totalPaymentVolume ), } ) } tracksSource="total_payment_volume" @@ -169,8 +169,8 @@ const PaymentActivityDataComponent: React.FC< Props > = ( { ) .add( siteTimeZone ) .format( 'YYYY-MM-DD' ), - ...getSearchParams( - searchTermsForViewReportLink.charge + ...getTypeFilters( + typeFiltersForViewReportLink.charge ), } ) } tracksSource="charges" @@ -196,8 +196,8 @@ const PaymentActivityDataComponent: React.FC< Props > = ( { ) .add( siteTimeZone ) .format( 'YYYY-MM-DD' ), - ...getSearchParams( - searchTermsForViewReportLink.refunds + ...getTypeFilters( + typeFiltersForViewReportLink.refunds ), } ) } tracksSource="refunds" @@ -250,8 +250,8 @@ const PaymentActivityDataComponent: React.FC< Props > = ( { ) .add( siteTimeZone ) .format( 'YYYY-MM-DD' ), - ...getSearchParams( - searchTermsForViewReportLink.dispute + ...getTypeFilters( + typeFiltersForViewReportLink.dispute ), } ) } tracksSource="disputes" diff --git a/client/components/payment-activity/test/__snapshots__/index.test.tsx.snap b/client/components/payment-activity/test/__snapshots__/index.test.tsx.snap index fd5ac782458..c4d9d6d7087 100644 --- a/client/components/payment-activity/test/__snapshots__/index.test.tsx.snap +++ b/client/components/payment-activity/test/__snapshots__/index.test.tsx.snap @@ -123,7 +123,7 @@ exports[`PaymentActivity component should render 1`] = `

View report @@ -183,7 +183,7 @@ exports[`PaymentActivity component should render 1`] = `

View report @@ -212,7 +212,7 @@ exports[`PaymentActivity component should render 1`] = `

View report @@ -269,7 +269,7 @@ exports[`PaymentActivity component should render 1`] = `

View report diff --git a/client/data/transactions/hooks.ts b/client/data/transactions/hooks.ts index e65ba57a03f..0b860612dc9 100644 --- a/client/data/transactions/hooks.ts +++ b/client/data/transactions/hooks.ts @@ -146,6 +146,7 @@ export const useTransactions = ( date_between: dateBetween, type_is: typeIs, type_is_not: typeIsNot, + type_is_in: typeIsIn, source_device_is: sourceDeviceIs, source_device_is_not: sourceDeviceIsNot, channel_is: channelIs, @@ -189,6 +190,7 @@ export const useTransactions = ( ), typeIs, typeIsNot, + typeIsIn, sourceDeviceIs, sourceDeviceIsNot, storeCurrencyIs, @@ -222,6 +224,7 @@ export const useTransactions = ( JSON.stringify( dateBetween ), typeIs, typeIsNot, + JSON.stringify( typeIsIn ), sourceDeviceIs, sourceDeviceIsNot, storeCurrencyIs, @@ -247,6 +250,7 @@ export const useTransactionsSummary = ( date_between: dateBetween, type_is: typeIs, type_is_not: typeIsNot, + type_is_in: typeIsIn, source_device_is: sourceDeviceIs, source_device_is_not: sourceDeviceIsNot, store_currency_is: storeCurrencyIs, @@ -276,6 +280,7 @@ export const useTransactionsSummary = ( dateBetween, typeIs, typeIsNot, + typeIsIn, sourceDeviceIs, sourceDeviceIsNot, storeCurrencyIs, @@ -304,6 +309,7 @@ export const useTransactionsSummary = ( JSON.stringify( dateBetween ), typeIs, typeIsNot, + JSON.stringify( typeIsIn ), sourceDeviceIs, sourceDeviceIsNot, storeCurrencyIs, diff --git a/client/data/transactions/resolvers.js b/client/data/transactions/resolvers.js index d2d6cab6cfb..77e517bdf6d 100644 --- a/client/data/transactions/resolvers.js +++ b/client/data/transactions/resolvers.js @@ -40,6 +40,7 @@ export const formatQueryFilters = ( query ) => ( { ], type_is: query.typeIs, type_is_not: query.typeIsNot, + type_is_in: query.typeIsIn, source_device_is: query.sourceDeviceIs, source_device_is_not: query.sourceDeviceIsNot, channel_is: query.channelIs, diff --git a/client/transactions/declarations.d.ts b/client/transactions/declarations.d.ts index 08ff94e4892..1ee8fe71627 100644 --- a/client/transactions/declarations.d.ts +++ b/client/transactions/declarations.d.ts @@ -128,6 +128,7 @@ declare module '@woocommerce/navigation' { date_between?: string[]; type_is?: unknown; type_is_not?: unknown; + type_is_in?: unknown; source_device_is?: unknown; source_device_is_not?: unknown; channel_is?: string; diff --git a/client/transactions/filters/config.ts b/client/transactions/filters/config.ts index b6e3c34f534..b5552654494 100644 --- a/client/transactions/filters/config.ts +++ b/client/transactions/filters/config.ts @@ -99,6 +99,7 @@ export const getFilters = ( 'filter', 'type_is', 'type_is_not', + 'type_is_in', 'date_before', 'date_after', 'date_between', diff --git a/includes/core/server/request/class-list-transactions.php b/includes/core/server/request/class-list-transactions.php index 4a2b998622e..3f369df29ba 100644 --- a/includes/core/server/request/class-list-transactions.php +++ b/includes/core/server/request/class-list-transactions.php @@ -82,6 +82,7 @@ function ( $transaction_date ) use ( $user_timezone ) { 'date_between' => $date_between_filter, 'type_is' => $request->get_param( 'type_is' ), 'type_is_not' => $request->get_param( 'type_is_not' ), + 'type_is_in' => (array) $request->get_param( 'type_is_in' ), 'source_device_is' => $request->get_param( 'source_device_is' ), 'source_device_is_not' => $request->get_param( 'source_device_is_not' ), 'channel_is' => $request->get_param( 'channel_is' ), From 776f752cad53c12a432d6cdcda515d10ec41405e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20L=C3=B3pez=20Ariza?= <45979455+alopezari@users.noreply.github.com> Date: Thu, 5 Dec 2024 08:57:04 +0100 Subject: [PATCH 14/83] Fix/php 8 compatibility errors warnings (#9719) --- .github/workflows/php-compatibility.yml | 3 +-- .../fix-php-8-compatibility-errors-warnings | 5 ++++ ...s-wc-rest-payments-settings-controller.php | 4 +-- includes/class-payment-information.php | 26 +++++++++---------- includes/class-wc-payment-gateway-wcpay.php | 8 +++--- .../class-wc-payments-customer-service.php | 2 +- ...s-wc-payments-explicit-price-formatter.php | 4 +-- includes/class-wc-payments-utils.php | 2 +- includes/class-wc-payments.php | 2 +- .../core/server/request/class-generic.php | 2 +- .../class-buyer-fingerprinting-service.php | 2 +- .../class-fraud-prevention-service.php | 4 +-- includes/multi-currency/MultiCurrency.php | 2 +- .../class-afterpay-payment-method.php | 4 +-- .../class-cc-payment-method.php | 2 +- .../class-upe-payment-method.php | 8 +++--- .../class-wc-payments-api-client.php | 2 +- .../Container/Argument/LiteralArgument.php | 2 +- lib/packages/League/Container/Container.php | 8 +++--- .../DefinitionContainerInterface.php | 2 +- .../League/Container/Inflector/Inflector.php | 2 +- .../Inflector/InflectorAggregate.php | 2 +- .../Inflector/InflectorAggregateInterface.php | 2 +- phpcs-compat.xml.dist | 3 +++ src/Container.php | 4 +-- src/Internal/Payment/PaymentContext.php | 2 +- src/Internal/Payment/PaymentRequest.php | 2 +- src/Internal/Service/OrderService.php | 2 +- ...s-wc-rest-payments-settings-controller.php | 4 +-- ...-class-wc-rest-payments-tos-controller.php | 4 +-- ...xpress-checkout-button-display-handler.php | 4 +-- ...ayments-express-checkout-button-helper.php | 4 +-- .../test-class-upe-payment-gateway.php | 6 ++--- .../test-class-upe-split-payment-gateway.php | 10 +++---- ...wc-payment-gateway-wcpay-payment-types.php | 2 +- ...-payment-gateway-wcpay-process-payment.php | 2 +- ...c-payment-gateway-wcpay-process-refund.php | 4 +-- ...ubscriptions-payment-method-order-note.php | 2 +- ...ay-wcpay-subscriptions-process-payment.php | 2 +- ...wc-payment-gateway-wcpay-subscriptions.php | 6 ++--- .../test-class-wc-payment-gateway-wcpay.php | 10 +++---- ...ayments-payment-request-button-handler.php | 4 +-- ...lass-wc-payments-woopay-button-handler.php | 4 +-- 43 files changed, 94 insertions(+), 87 deletions(-) create mode 100644 changelog/fix-php-8-compatibility-errors-warnings diff --git a/.github/workflows/php-compatibility.yml b/.github/workflows/php-compatibility.yml index 41d87e5b1db..5d018dabc37 100644 --- a/.github/workflows/php-compatibility.yml +++ b/.github/workflows/php-compatibility.yml @@ -1,8 +1,7 @@ name: PHP Compatibility on: - #pull_request # Workflow disabled temporarily until PHP Compatibility fixes are in place - workflow_dispatch + pull_request concurrency: group: ${{ github.workflow }}-${{ github.ref }} diff --git a/changelog/fix-php-8-compatibility-errors-warnings b/changelog/fix-php-8-compatibility-errors-warnings new file mode 100644 index 00000000000..9c393f71654 --- /dev/null +++ b/changelog/fix-php-8-compatibility-errors-warnings @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: These changes fix some PHP compatibility errors that don't impact WooPayments behaviour. + + diff --git a/includes/admin/class-wc-rest-payments-settings-controller.php b/includes/admin/class-wc-rest-payments-settings-controller.php index dfd6e76f005..1e6c21a54e3 100644 --- a/includes/admin/class-wc-rest-payments-settings-controller.php +++ b/includes/admin/class-wc-rest-payments-settings-controller.php @@ -1042,7 +1042,7 @@ private function update_is_stripe_billing_enabled( WP_REST_Request $request ) { * * @return WP_REST_Response|null The response object, if this is a REST request. */ - public function schedule_stripe_billing_migration( WP_REST_Request $request = null ) { + public function schedule_stripe_billing_migration( ?WP_REST_Request $request = null ) { if ( class_exists( 'WC_Payments_Subscriptions' ) ) { $stripe_billing_migrator = WC_Payments_Subscriptions::get_stripe_billing_migrator(); @@ -1065,7 +1065,7 @@ public function schedule_stripe_billing_migration( WP_REST_Request $request = nu * * @return WP_REST_Response|WP_Error The response object, if this is a REST request. */ - public function request_capability( WP_REST_Request $request = null ) { + public function request_capability( ?WP_REST_Request $request = null ) { $request_result = null; $id = $request->get_param( 'id' ); $capability_key_map = $this->wcpay_gateway->get_payment_method_capability_key_map(); diff --git a/includes/class-payment-information.php b/includes/class-payment-information.php index f3cc00d85ef..4b4f8a13f04 100644 --- a/includes/class-payment-information.php +++ b/includes/class-payment-information.php @@ -144,15 +144,15 @@ class Payment_Information { */ public function __construct( string $payment_method, - \WC_Order $order = null, - Payment_Type $payment_type = null, - \WC_Payment_Token $token = null, - Payment_Initiated_By $payment_initiated_by = null, - Payment_Capture_Type $manual_capture = null, - string $cvc_confirmation = null, + ?\WC_Order $order = null, + ?Payment_Type $payment_type = null, + ?\WC_Payment_Token $token = null, + ?Payment_Initiated_By $payment_initiated_by = null, + ?Payment_Capture_Type $manual_capture = null, + ?string $cvc_confirmation = null, string $fingerprint = '', - string $payment_method_stripe_id = null, - string $customer_id = null + ?string $payment_method_stripe_id = null, + ?string $customer_id = null ) { if ( empty( $payment_method ) && empty( $token ) && ! \WC_Payments::is_network_saved_cards_enabled() ) { // If network-wide cards are enabled, a payment method or token may not be specified and the platform default one will be used. @@ -259,11 +259,11 @@ public function is_using_manual_capture(): bool { */ public static function from_payment_request( array $request, - \WC_Order $order = null, - Payment_Type $payment_type = null, - Payment_Initiated_By $payment_initiated_by = null, - Payment_Capture_Type $manual_capture = null, - string $payment_method_stripe_id = null + ?\WC_Order $order = null, + ?Payment_Type $payment_type = null, + ?Payment_Initiated_By $payment_initiated_by = null, + ?Payment_Capture_Type $manual_capture = null, + ?string $payment_method_stripe_id = null ): Payment_Information { $payment_method = self::get_payment_method_from_request( $request ); $token = self::get_token_from_request( $request ); diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index e68cc4469d7..02d26bbcb4c 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -271,12 +271,12 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC { * @param WC_Payments_Action_Scheduler_Service $action_scheduler_service - Action Scheduler service instance. * @param UPE_Payment_Method $payment_method - Specific UPE_Payment_Method instance for gateway. * @param array $payment_methods - Array of UPE payment methods. - * @param Session_Rate_Limiter|null $failed_transaction_rate_limiter - Rate Limiter for failed transactions. * @param WC_Payments_Order_Service $order_service - Order class instance. * @param Duplicate_Payment_Prevention_Service $duplicate_payment_prevention_service - Service for preventing duplicate payments. * @param WC_Payments_Localization_Service $localization_service - Localization service instance. * @param WC_Payments_Fraud_Service $fraud_service - Fraud service instance. * @param Duplicates_Detection_Service $duplicate_payment_methods_detection_service - Service for finding duplicate enabled payment methods. + * @param Session_Rate_Limiter|null $failed_transaction_rate_limiter - Rate Limiter for failed transactions. */ public function __construct( WC_Payments_API_Client $payments_api_client, @@ -286,12 +286,12 @@ public function __construct( WC_Payments_Action_Scheduler_Service $action_scheduler_service, UPE_Payment_Method $payment_method, array $payment_methods, - Session_Rate_Limiter $failed_transaction_rate_limiter = null, WC_Payments_Order_Service $order_service, Duplicate_Payment_Prevention_Service $duplicate_payment_prevention_service, WC_Payments_Localization_Service $localization_service, WC_Payments_Fraud_Service $fraud_service, - Duplicates_Detection_Service $duplicate_payment_methods_detection_service + Duplicates_Detection_Service $duplicate_payment_methods_detection_service, + ?Session_Rate_Limiter $failed_transaction_rate_limiter = null ) { $this->payment_methods = $payment_methods; $this->payment_method = $payment_method; @@ -3763,7 +3763,7 @@ public function schedule_order_tracking( $order_id, $order = null ) { * * @throws Exception - When an error occurs in intent creation. */ - public function create_intent( WC_Order $order, array $payment_methods, string $capture_method = 'automatic', array $metadata = [], string $customer_id = null ) { + public function create_intent( WC_Order $order, array $payment_methods, string $capture_method = 'automatic', array $metadata = [], ?string $customer_id = null ) { $currency = strtolower( $order->get_currency() ); $converted_amount = WC_Payments_Utils::prepare_amount( $order->get_total(), $currency ); $order_number = $order->get_order_number(); diff --git a/includes/class-wc-payments-customer-service.php b/includes/class-wc-payments-customer-service.php index 42d209fd3fd..05f95c32d31 100644 --- a/includes/class-wc-payments-customer-service.php +++ b/includes/class-wc-payments-customer-service.php @@ -331,7 +331,7 @@ public function clear_cached_payment_methods_for_user( $user_id ) { * * @return array Customer data. */ - public static function map_customer_data( WC_Order $wc_order = null, WC_Customer $wc_customer = null ): array { + public static function map_customer_data( ?WC_Order $wc_order = null, ?WC_Customer $wc_customer = null ): array { if ( null === $wc_customer && null === $wc_order ) { return []; } diff --git a/includes/class-wc-payments-explicit-price-formatter.php b/includes/class-wc-payments-explicit-price-formatter.php index 31c5364cfbe..8c79f45a8aa 100644 --- a/includes/class-wc-payments-explicit-price-formatter.php +++ b/includes/class-wc-payments-explicit-price-formatter.php @@ -107,7 +107,7 @@ public static function unregister_formatted_woocommerce_price_filter() { * * @return string */ - public static function get_explicit_price( string $price, WC_Abstract_Order $order = null ) { + public static function get_explicit_price( string $price, ?WC_Abstract_Order $order = null ) { if ( null === $order ) { $currency_code = get_woocommerce_currency(); } else { @@ -136,7 +136,7 @@ public static function get_explicit_price_with_currency( string $price, ?string return $price; } - $price_to_check = html_entity_decode( wp_strip_all_tags( $price ) ); + $price_to_check = html_entity_decode( wp_strip_all_tags( $price ), ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ); if ( false === strpos( $price_to_check, trim( $currency_code ) ) ) { return $price . ' ' . $currency_code; diff --git a/includes/class-wc-payments-utils.php b/includes/class-wc-payments-utils.php index 7afbc0e5835..24608d2c898 100644 --- a/includes/class-wc-payments-utils.php +++ b/includes/class-wc-payments-utils.php @@ -431,7 +431,7 @@ public static function array_map_recursive( array $array, callable $callback ): * * @return array The filtered array. */ - public static function array_filter_recursive( array $array, callable $callback = null ): array { + public static function array_filter_recursive( array $array, ?callable $callback = null ): array { foreach ( $array as $key => &$value ) { // Mind the use of a reference. if ( \is_array( $value ) ) { $value = self::array_filter_recursive( $value, $callback ); diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 66e72bb8dbf..103ead1e52d 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -579,7 +579,7 @@ public static function init() { foreach ( $payment_methods as $payment_method ) { self::$payment_method_map[ $payment_method->get_id() ] = $payment_method; - $split_gateway = new WC_Payment_Gateway_WCPay( self::$api_client, self::$account, self::$customer_service, self::$token_service, self::$action_scheduler_service, $payment_method, $payment_methods, self::$failed_transaction_rate_limiter, self::$order_service, self::$duplicate_payment_prevention_service, self::$localization_service, self::$fraud_service, self::$duplicates_detection_service ); + $split_gateway = new WC_Payment_Gateway_WCPay( self::$api_client, self::$account, self::$customer_service, self::$token_service, self::$action_scheduler_service, $payment_method, $payment_methods, self::$order_service, self::$duplicate_payment_prevention_service, self::$localization_service, self::$fraud_service, self::$duplicates_detection_service, self::$failed_transaction_rate_limiter ); // Card gateway hooks are registered once below. if ( 'card' !== $payment_method->get_id() ) { diff --git a/includes/core/server/request/class-generic.php b/includes/core/server/request/class-generic.php index 36543990864..0cc19d4bf24 100644 --- a/includes/core/server/request/class-generic.php +++ b/includes/core/server/request/class-generic.php @@ -58,7 +58,7 @@ public static function create( $id = null ) { * @param array $parameters The parameters for the request. * @throws Invalid_Request_Parameter_Exception An exception if there are invalid properties. */ - public function __construct( string $api, string $method, array $parameters = null ) { + public function __construct( string $api, string $method, ?array $parameters = null ) { if ( ! defined( \WC_Payments_Utils::get_wpcore_request_class() . "::$method" ) ) { throw new Invalid_Request_Parameter_Exception( 'Invalid generic request method', 'wcpay_core_invalid_request_parameter_method_not_defined' ); } diff --git a/includes/fraud-prevention/class-buyer-fingerprinting-service.php b/includes/fraud-prevention/class-buyer-fingerprinting-service.php index 0976a49c2ee..60b9c93d940 100644 --- a/includes/fraud-prevention/class-buyer-fingerprinting-service.php +++ b/includes/fraud-prevention/class-buyer-fingerprinting-service.php @@ -39,7 +39,7 @@ public static function get_instance(): self { * * @param Buyer_Fingerprinting_Service|null $instance Instance of self. */ - public static function set_instance( self $instance = null ) { + public static function set_instance( ?self $instance = null ) { self::$instance = $instance; } diff --git a/includes/fraud-prevention/class-fraud-prevention-service.php b/includes/fraud-prevention/class-fraud-prevention-service.php index db783718a44..97e994836b6 100644 --- a/includes/fraud-prevention/class-fraud-prevention-service.php +++ b/includes/fraud-prevention/class-fraud-prevention-service.php @@ -118,7 +118,7 @@ public function is_pay_for_order_page() { * * @param Fraud_Prevention_Service|null $instance Instance of self. */ - public static function set_instance( self $instance = null ) { + public static function set_instance( ?self $instance = null ) { self::$instance = $instance; } @@ -164,7 +164,7 @@ public function regenerate_token(): string { * @param string|null $token Token sent in request. * @return bool */ - public function verify_token( string $token = null ): bool { + public function verify_token( ?string $token = null ): bool { $session_token = $this->session->get( self::TOKEN_NAME ); // Check if the tokens are both strings. diff --git a/includes/multi-currency/MultiCurrency.php b/includes/multi-currency/MultiCurrency.php index 9313a987dc9..301503d9ca0 100644 --- a/includes/multi-currency/MultiCurrency.php +++ b/includes/multi-currency/MultiCurrency.php @@ -188,7 +188,7 @@ class MultiCurrency { * @param MultiCurrencyCacheInterface $cache Cache instance. * @param Utils|null $utils Optional Utils instance. */ - public function __construct( MultiCurrencySettingsInterface $settings_service, MultiCurrencyApiClientInterface $payments_api_client, MultiCurrencyAccountInterface $payments_account, MultiCurrencyLocalizationInterface $localization_service, MultiCurrencyCacheInterface $cache, Utils $utils = null ) { + public function __construct( MultiCurrencySettingsInterface $settings_service, MultiCurrencyApiClientInterface $payments_api_client, MultiCurrencyAccountInterface $payments_account, MultiCurrencyLocalizationInterface $localization_service, MultiCurrencyCacheInterface $cache, ?Utils $utils = null ) { $this->settings_service = $settings_service; $this->payments_api_client = $payments_api_client; $this->payments_account = $payments_account; diff --git a/includes/payment-methods/class-afterpay-payment-method.php b/includes/payment-methods/class-afterpay-payment-method.php index 4cc9b027e8c..3674731835c 100644 --- a/includes/payment-methods/class-afterpay-payment-method.php +++ b/includes/payment-methods/class-afterpay-payment-method.php @@ -46,7 +46,7 @@ public function __construct( $token_service ) { * * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable */ - public function get_title( string $account_country = null, $payment_details = false ) { + public function get_title( ?string $account_country = null, $payment_details = false ) { if ( 'GB' === $account_country ) { return __( 'Clearpay', 'woocommerce-payments' ); } @@ -60,7 +60,7 @@ public function get_title( string $account_country = null, $payment_details = fa * @param string|null $account_country Country of merchants account. * @return string|null */ - public function get_icon( string $account_country = null ) { + public function get_icon( ?string $account_country = null ) { if ( 'GB' === $account_country ) { return plugins_url( 'assets/images/payment-methods/clearpay.svg', WCPAY_PLUGIN_FILE ); } diff --git a/includes/payment-methods/class-cc-payment-method.php b/includes/payment-methods/class-cc-payment-method.php index 3f0c114aa8a..50a44fa1114 100644 --- a/includes/payment-methods/class-cc-payment-method.php +++ b/includes/payment-methods/class-cc-payment-method.php @@ -38,7 +38,7 @@ public function __construct( $token_service ) { * @param array|false $payment_details Payment details. * @return string */ - public function get_title( string $account_country = null, $payment_details = false ) { + public function get_title( ?string $account_country = null, $payment_details = false ) { if ( ! $payment_details ) { return $this->title; } diff --git a/includes/payment-methods/class-upe-payment-method.php b/includes/payment-methods/class-upe-payment-method.php index 2e69e916c1b..02e8c0984ae 100644 --- a/includes/payment-methods/class-upe-payment-method.php +++ b/includes/payment-methods/class-upe-payment-method.php @@ -133,7 +133,7 @@ public function get_id() { * * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable */ - public function get_title( string $account_country = null, $payment_details = false ) { + public function get_title( ?string $account_country = null, $payment_details = false ) { return $this->title; } @@ -283,7 +283,7 @@ abstract public function get_testing_instructions( string $account_country ); * * @phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable */ - public function get_icon( string $account_country = null ) { + public function get_icon( ?string $account_country = null ) { return isset( $this->icon_url ) ? $this->icon_url : ''; } @@ -293,7 +293,7 @@ public function get_icon( string $account_country = null ) { * @param string|null $account_country Optional account country. * @return string */ - public function get_dark_icon( string $account_country = null ) { + public function get_dark_icon( ?string $account_country = null ) { return isset( $this->dark_icon_url ) ? $this->dark_icon_url : $this->get_icon( $account_country ); } @@ -305,7 +305,7 @@ public function get_dark_icon( string $account_country = null ) { * @param string $account_country Optional account country. * @return string */ - public function get_payment_method_icon_for_location( string $location = 'checkout', bool $is_blocks = true, string $account_country = null ) { + public function get_payment_method_icon_for_location( string $location = 'checkout', bool $is_blocks = true, ?string $account_country = null ) { $appearance_theme = WC_Payments_Utils::get_active_upe_theme_transient_for_location( $location, $is_blocks ? 'blocks' : 'classic' ); if ( 'night' === $appearance_theme ) { 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 13b25e07dd6..01c57eb8969 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -1702,7 +1702,7 @@ public function register_domain( $domain_name ) { * * @throws API_Exception If an error occurs. */ - public function register_terminal_reader( string $location, string $registration_code, string $label = null, array $metadata = null ) { + public function register_terminal_reader( string $location, string $registration_code, ?string $label = null, ?array $metadata = null ) { $request = [ 'location' => $location, 'registration_code' => $registration_code, diff --git a/lib/packages/League/Container/Argument/LiteralArgument.php b/lib/packages/League/Container/Argument/LiteralArgument.php index a24f1c5625e..fef5f2a0b41 100644 --- a/lib/packages/League/Container/Argument/LiteralArgument.php +++ b/lib/packages/League/Container/Argument/LiteralArgument.php @@ -24,7 +24,7 @@ class LiteralArgument implements LiteralArgumentInterface */ protected $value; - public function __construct($value, string $type = null) + public function __construct($value, ?string $type = null) { if ( null === $type diff --git a/lib/packages/League/Container/Container.php b/lib/packages/League/Container/Container.php index 7c166e74550..9094b686161 100644 --- a/lib/packages/League/Container/Container.php +++ b/lib/packages/League/Container/Container.php @@ -40,9 +40,9 @@ class Container implements DefinitionContainerInterface protected $delegates = []; public function __construct( - DefinitionAggregateInterface $definitions = null, - ServiceProviderAggregateInterface $providers = null, - InflectorAggregateInterface $inflectors = null + ?DefinitionAggregateInterface $definitions = null, + ?ServiceProviderAggregateInterface $providers = null, + ?InflectorAggregateInterface $inflectors = null ) { $this->definitions = $definitions ?? new DefinitionAggregate(); $this->providers = $providers ?? new ServiceProviderAggregate(); @@ -139,7 +139,7 @@ public function has($id): bool return false; } - public function inflector(string $type, callable $callback = null): InflectorInterface + public function inflector(string $type, ?callable $callback = null): InflectorInterface { return $this->inflectors->add($type, $callback); } diff --git a/lib/packages/League/Container/DefinitionContainerInterface.php b/lib/packages/League/Container/DefinitionContainerInterface.php index 35dfd8c9b9c..c8ac0c6c3cf 100644 --- a/lib/packages/League/Container/DefinitionContainerInterface.php +++ b/lib/packages/League/Container/DefinitionContainerInterface.php @@ -16,5 +16,5 @@ public function addServiceProvider(ServiceProviderInterface $provider): self; public function addShared(string $id, $concrete = null): DefinitionInterface; public function extend(string $id): DefinitionInterface; public function getNew($id); - public function inflector(string $type, callable $callback = null): InflectorInterface; + public function inflector(string $type, ?callable $callback = null): InflectorInterface; } diff --git a/lib/packages/League/Container/Inflector/Inflector.php b/lib/packages/League/Container/Inflector/Inflector.php index d9273aa99d4..b484f86b933 100644 --- a/lib/packages/League/Container/Inflector/Inflector.php +++ b/lib/packages/League/Container/Inflector/Inflector.php @@ -33,7 +33,7 @@ class Inflector implements ArgumentResolverInterface, InflectorInterface */ protected $properties = []; - public function __construct(string $type, callable $callback = null) + public function __construct(string $type, ?callable $callback = null) { $this->type = $type; $this->callback = $callback; diff --git a/lib/packages/League/Container/Inflector/InflectorAggregate.php b/lib/packages/League/Container/Inflector/InflectorAggregate.php index 4d32edcdcde..a8bdd0d49e0 100644 --- a/lib/packages/League/Container/Inflector/InflectorAggregate.php +++ b/lib/packages/League/Container/Inflector/InflectorAggregate.php @@ -16,7 +16,7 @@ class InflectorAggregate implements InflectorAggregateInterface */ protected $inflectors = []; - public function add(string $type, callable $callback = null): Inflector + public function add(string $type, ?callable $callback = null): Inflector { $inflector = new Inflector($type, $callback); $this->inflectors[] = $inflector; diff --git a/lib/packages/League/Container/Inflector/InflectorAggregateInterface.php b/lib/packages/League/Container/Inflector/InflectorAggregateInterface.php index ce8a6766277..c8ad57ea2bf 100644 --- a/lib/packages/League/Container/Inflector/InflectorAggregateInterface.php +++ b/lib/packages/League/Container/Inflector/InflectorAggregateInterface.php @@ -9,6 +9,6 @@ interface InflectorAggregateInterface extends ContainerAwareInterface, IteratorAggregate { - public function add(string $type, callable $callback = null): Inflector; + public function add(string $type, ?callable $callback = null): Inflector; public function inflect(object $object); } diff --git a/phpcs-compat.xml.dist b/phpcs-compat.xml.dist index 83faef2a44a..7c096864345 100644 --- a/phpcs-compat.xml.dist +++ b/phpcs-compat.xml.dist @@ -26,5 +26,8 @@ + + + diff --git a/src/Container.php b/src/Container.php index a7a42f7e0d1..2a866510f62 100644 --- a/src/Container.php +++ b/src/Container.php @@ -70,8 +70,8 @@ class Container implements ContainerInterface { * @param WooContainer $woo_container Delegate container for WooCommerce (Optional). */ public function __construct( - LegacyContainer $legacy_container = null, - WooContainer $woo_container = null + ?LegacyContainer $legacy_container = null, + ?WooContainer $woo_container = null ) { $this->container = new ExtendedContainer(); diff --git a/src/Internal/Payment/PaymentContext.php b/src/Internal/Payment/PaymentContext.php index 7fe6659a12c..9edd9aa35f9 100644 --- a/src/Internal/Payment/PaymentContext.php +++ b/src/Internal/Payment/PaymentContext.php @@ -151,7 +151,7 @@ public function get_metadata(): ?array { * * @param string $cvc_confirmation The confirmation. */ - public function set_cvc_confirmation( string $cvc_confirmation = null ) { + public function set_cvc_confirmation( ?string $cvc_confirmation = null ) { $this->set( 'cvc_confirmation', $cvc_confirmation ); } diff --git a/src/Internal/Payment/PaymentRequest.php b/src/Internal/Payment/PaymentRequest.php index d6c0c4397f0..79c62bdc2fc 100644 --- a/src/Internal/Payment/PaymentRequest.php +++ b/src/Internal/Payment/PaymentRequest.php @@ -39,7 +39,7 @@ class PaymentRequest { * @param LegacyProxy $legacy_proxy Legacy proxy. * @param array|null $request Request data, this can be $_POST, or WP_REST_Request::get_params(). */ - public function __construct( LegacyProxy $legacy_proxy, array $request = null ) { + public function __construct( LegacyProxy $legacy_proxy, ?array $request = null ) { $this->legacy_proxy = $legacy_proxy; // phpcs:ignore WordPress.Security.NonceVerification.Missing $this->request = $request ?? $_POST; diff --git a/src/Internal/Service/OrderService.php b/src/Internal/Service/OrderService.php index 907b24ba28d..99b869835b8 100644 --- a/src/Internal/Service/OrderService.php +++ b/src/Internal/Service/OrderService.php @@ -118,7 +118,7 @@ public function set_payment_method_id( int $order_id, string $payment_method_id * @return array The metadat athat will be sent to the server. * @throws Order_Not_Found_Exception */ - public function get_payment_metadata( int $order_id, Payment_Type $payment_type = null ) { + public function get_payment_metadata( int $order_id, ?Payment_Type $payment_type = null ) { $order = $this->get_order( $order_id ); $name = sanitize_text_field( $order->get_billing_first_name() ) . ' ' . sanitize_text_field( $order->get_billing_last_name() ); diff --git a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php index 459a6a7bf08..963ba367fac 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-settings-controller.php @@ -177,12 +177,12 @@ public function set_up() { $action_scheduler_service, $mock_payment_method, $mock_payment_methods, - $mock_rate_limiter, $order_service, $mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, - $this->mock_duplicates_detection_service + $this->mock_duplicates_detection_service, + $mock_rate_limiter ); $this->controller = new WC_REST_Payments_Settings_Controller( $this->mock_api_client, $this->gateway, $this->mock_wcpay_account ); diff --git a/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php b/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php index 20e4cf57169..08b6d5a0f4f 100644 --- a/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php +++ b/tests/unit/admin/test-class-wc-rest-payments-tos-controller.php @@ -78,12 +78,12 @@ public function set_up() { $action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $mock_rate_limiter, $order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $mock_fraud_service, - $mock_duplicates_detection_service + $mock_duplicates_detection_service, + $mock_rate_limiter ); $this->controller = new WC_REST_Payments_Tos_Controller( $mock_api_client, $this->gateway, $mock_wcpay_account ); diff --git a/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-display-handler.php b/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-display-handler.php index 482faafe057..c0c807f8481 100644 --- a/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-display-handler.php +++ b/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-display-handler.php @@ -207,12 +207,12 @@ private function make_wcpay_gateway() { $mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $mock_rate_limiter, $mock_order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), - $this->createMock( Duplicates_Detection_Service::class ) + $this->createMock( Duplicates_Detection_Service::class ), + $mock_rate_limiter ); } 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 bcc4ca69601..2432c61172c 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 @@ -178,12 +178,12 @@ private function make_wcpay_gateway() { $mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $mock_rate_limiter, $mock_order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), - $this->createMock( Duplicates_Detection_Service::class ) + $this->createMock( Duplicates_Detection_Service::class ), + $mock_rate_limiter ); } diff --git a/tests/unit/payment-methods/test-class-upe-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-payment-gateway.php index 91000a6490c..000f5eade08 100644 --- a/tests/unit/payment-methods/test-class-upe-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-payment-gateway.php @@ -298,12 +298,12 @@ public function set_up() { $this->mock_action_scheduler_service, $this->mock_payment_method, $this->mock_payment_methods, - $this->mock_rate_limiter, $this->mock_order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->setMethods( @@ -970,12 +970,12 @@ public function test_get_upe_available_payment_methods( $payment_methods, $expec $this->mock_action_scheduler_service, $this->mock_payment_method, $this->mock_payment_methods, - $this->mock_rate_limiter, $this->mock_order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, - $this->mock_duplicates_detection_service + $this->mock_duplicates_detection_service, + $this->mock_rate_limiter ); $this->assertEquals( $expected_result, $gateway->get_upe_available_payment_methods() ); diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php index 8ac1db139a5..9d6185387f6 100644 --- a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php @@ -285,12 +285,12 @@ public function set_up() { $this->mock_action_scheduler_service, $mock_payment_method, $this->mock_payment_methods, - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->setMethods( @@ -1065,12 +1065,12 @@ public function test_get_payment_methods_with_request_context() { $this->mock_action_scheduler_service, $this->mock_payment_methods[ Payment_Method::CARD ], $this->mock_payment_methods, - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->setMethods( [ 'get_payment_methods_from_gateway_id' ] ) @@ -1111,12 +1111,12 @@ public function test_get_payment_methods_without_request_context() { $this->mock_action_scheduler_service, $this->mock_payment_methods[ Payment_Method::CARD ], $this->mock_payment_methods, - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->setMethods( [ 'get_payment_methods_from_gateway_id' ] ) @@ -1156,12 +1156,12 @@ public function test_get_payment_methods_without_request_context_or_token() { $this->mock_action_scheduler_service, $this->mock_payment_methods[ Payment_Method::CARD ], $this->mock_payment_methods, - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->setMethods( @@ -1210,12 +1210,12 @@ public function test_get_payment_methods_from_gateway_id_upe() { $this->mock_action_scheduler_service, $this->mock_payment_methods[ Payment_Method::CARD ], $this->mock_payment_methods, - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->onlyMethods( diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php b/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php index c4ae5f729ee..e2d79359c57 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-payment-types.php @@ -149,12 +149,12 @@ public function set_up() { $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_rate_limiter, $this->mock_order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), $this->createMock( Duplicates_Detection_Service::class ), + $this->mock_rate_limiter, ] ) ->setMethods( diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php index 771e3475288..39e94cba660 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-payment.php @@ -161,12 +161,12 @@ public function set_up() { $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_rate_limiter, $this->mock_order_service, $this->mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), $this->createMock( Duplicates_Detection_Service::class ), + $this->mock_rate_limiter, ] ) ->setMethods( diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php b/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php index 24f06d99933..b75d1721c16 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-process-refund.php @@ -101,12 +101,12 @@ public function set_up() { $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_rate_limiter, $this->mock_order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), - $this->createMock( Duplicates_Detection_Service::class ) + $this->createMock( Duplicates_Detection_Service::class ), + $this->mock_rate_limiter ); } diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php index fd4352ac0b2..1248431c4aa 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-payment-method-order-note.php @@ -136,12 +136,12 @@ public function set_up() { $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_session_rate_limiter, $this->mock_order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), $this->createMock( Duplicates_Detection_Service::class ), + $this->mock_session_rate_limiter ); $this->wcpay_gateway->init_hooks(); diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php index 622e7cbe1d9..8be72c70d74 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions-process-payment.php @@ -156,12 +156,12 @@ public function set_up() { $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_rate_limiter, $this->order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), $this->createMock( Duplicates_Detection_Service::class ), + $this->mock_rate_limiter, ] ) ->setMethods( diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php index 8d011f7f508..fe96d9d9834 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay-subscriptions.php @@ -161,12 +161,12 @@ public function set_up() { $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_session_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_session_rate_limiter ); $this->wcpay_gateway->init_hooks(); WC_Payments::set_gateway( $this->wcpay_gateway ); @@ -906,12 +906,12 @@ public function test_adds_custom_payment_meta_input_fallback_until_subs_3_0_7() $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_session_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_session_rate_limiter ); // Ensure the has_attached_integration_hooks property is set to false so callbacks can be attached in maybe_init_subscriptions(). @@ -941,12 +941,12 @@ public function test_does_not_add_custom_payment_meta_input_fallback_for_subs_3_ $this->mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $this->mock_session_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_session_rate_limiter ); $this->assertFalse( has_action( 'woocommerce_admin_order_data_after_billing_address' ) ); diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index bb90e4f4460..4b7bf857997 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -1016,12 +1016,12 @@ public function test_process_redirect_setup_intent_succeded() { $this->mock_action_scheduler_service, $this->payment_methods['card'], [ $this->payment_methods ], - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->onlyMethods( @@ -1124,12 +1124,12 @@ public function test_process_redirect_payment_save_payment_token() { $this->mock_action_scheduler_service, $this->payment_methods['card'], [ $this->payment_methods ], - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ] ) ->onlyMethods( @@ -3559,12 +3559,12 @@ private function get_partial_mock_for_gateway( array $methods = [], array $const $this->mock_action_scheduler_service, $this->mock_payment_method, [ $this->mock_payment_method ], - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, $this->mock_duplicates_detection_service, + $this->mock_rate_limiter, ]; foreach ( $constructor_replacement as $key => $value ) { @@ -4093,12 +4093,12 @@ private function init_gateways() { $this->mock_action_scheduler_service, $payment_method, $this->payment_methods, - $this->mock_rate_limiter, $this->order_service, $this->mock_dpps, $this->mock_localization_service, $this->mock_fraud_service, - $this->mock_duplicates_detection_service + $this->mock_duplicates_detection_service, + $this->mock_rate_limiter ); } diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php index 28bfdfb064e..b34299b76f6 100644 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ b/tests/unit/test-class-wc-payments-payment-request-button-handler.php @@ -222,12 +222,12 @@ private function make_wcpay_gateway() { $mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $mock_rate_limiter, $mock_order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), - $this->createMock( Duplicates_Detection_Service::class ) + $this->createMock( Duplicates_Detection_Service::class ), + $mock_rate_limiter ); } diff --git a/tests/unit/test-class-wc-payments-woopay-button-handler.php b/tests/unit/test-class-wc-payments-woopay-button-handler.php index 82b33b7a683..ba548ddfcd7 100644 --- a/tests/unit/test-class-wc-payments-woopay-button-handler.php +++ b/tests/unit/test-class-wc-payments-woopay-button-handler.php @@ -163,12 +163,12 @@ private function make_wcpay_gateway() { $mock_action_scheduler_service, $mock_payment_method, [ 'card' => $mock_payment_method ], - $mock_rate_limiter, $mock_order_service, $mock_dpps, $this->createMock( WC_Payments_Localization_Service::class ), $this->createMock( WC_Payments_Fraud_Service::class ), - $this->createMock( Duplicates_Detection_Service::class ) + $this->createMock( Duplicates_Detection_Service::class ), + $mock_rate_limiter ); } From 9a654de68420bb8efa163c3c4cfae09468299e61 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Thu, 5 Dec 2024 10:24:16 +0100 Subject: [PATCH 15/83] Migrate to custom containers and better elements scoping (#9782) Co-authored-by: Timur Karimov Co-authored-by: Francesco --- changelog/scope-payment-elements-selectors | 4 + client/checkout/classic/event-handlers.js | 110 ++------ client/checkout/classic/payment-processing.js | 2 +- client/checkout/utils/test/upe.test.js | 178 ++++++++----- client/checkout/utils/upe.js | 238 +++++++++++++----- includes/class-wc-payments-checkout.php | 41 +-- .../unit/test-class-wc-payments-checkout.php | 2 +- 7 files changed, 345 insertions(+), 230 deletions(-) create mode 100644 changelog/scope-payment-elements-selectors diff --git a/changelog/scope-payment-elements-selectors b/changelog/scope-payment-elements-selectors new file mode 100644 index 00000000000..515bb60dc2e --- /dev/null +++ b/changelog/scope-payment-elements-selectors @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Ensure more robust selectors scoping to improve theme compatibility. diff --git a/client/checkout/classic/event-handlers.js b/client/checkout/classic/event-handlers.js index fe53b9b2a88..ca00bcbd7a6 100644 --- a/client/checkout/classic/event-handlers.js +++ b/client/checkout/classic/event-handlers.js @@ -1,4 +1,4 @@ -/* global jQuery, wc_address_i18n_params */ +/* global jQuery */ /** * Internal dependencies @@ -12,6 +12,7 @@ import { hasPaymentMethodCountryRestrictions, isUsingSavedPaymentMethod, togglePaymentMethodForCountry, + isBillingInformationMissing, } from '../utils/upe'; import { processPayment, @@ -30,20 +31,10 @@ import apiRequest from '../utils/request'; import { handleWooPayEmailInput } from 'wcpay/checkout/woopay/email-input-iframe'; import { isPreviewing } from 'wcpay/checkout/preview'; import { recordUserEvent } from 'tracks'; -import { SHORTCODE_BILLING_ADDRESS_FIELDS } from 'wcpay/checkout/constants'; import '../utils/copy-test-number'; +import { SHORTCODE_BILLING_ADDRESS_FIELDS } from '../constants'; -function getParsedLocale() { - try { - return JSON.parse( - wc_address_i18n_params.locale.replace( /"/g, '"' ) - ); - } catch ( e ) { - return null; - } -} jQuery( function ( $ ) { - const locale = getParsedLocale(); enqueueFraudScripts( getUPEConfig( 'fraudServices' ) ); const publishableKey = getUPEConfig( 'publishableKey' ); @@ -85,7 +76,7 @@ jQuery( function ( $ ) { } ); $checkoutForm.on( generateCheckoutEventNames(), function () { - if ( isBillingInformationMissing() ) { + if ( isBillingInformationMissing( this ) ) { return; } @@ -93,11 +84,10 @@ jQuery( function ( $ ) { } ); $checkoutForm.on( 'click', '#place_order', function () { - const isWCPay = document.getElementById( - 'payment_method_woocommerce_payments' - )?.checked; + // Use the existing utility function to check if any WCPay payment method is selected + const selectedPaymentMethod = getSelectedUPEGatewayPaymentMethod(); - if ( ! isWCPay ) { + if ( ! selectedPaymentMethod ) { return; } @@ -231,11 +221,11 @@ jQuery( function ( $ ) { } async function maybeMountStripePaymentElement( elementsLocation ) { - if ( - $( '.wcpay-upe-element' ).length && - ! $( '.wcpay-upe-element' ).children().length - ) { - for ( const upeElement of $( '.wcpay-upe-element' ).toArray() ) { + const $upeForms = $( '.wcpay-upe-form' ); + const $upeElements = $upeForms.find( '.wcpay-upe-element' ); + + if ( $upeElements.length && ! $upeElements.children().length ) { + for ( const upeElement of $upeElements.toArray() ) { await mountStripePaymentElement( api, upeElement, @@ -251,71 +241,17 @@ jQuery( function ( $ ) { if ( hasPaymentMethodCountryRestrictions( upeElement ) ) { togglePaymentMethodForCountry( upeElement ); - // this event only applies to the checkout form, but not "place order" or "add payment method" pages. - $( '#billing_country' ).on( 'change', function () { - togglePaymentMethodForCountry( upeElement ); - } ); - } - } - - function isBillingInformationMissing() { - const enabledBillingFields = getUPEConfig( 'enabledBillingFields' ); - - // first name and last name are kinda special - we just need one of them to be at checkout - const name = `${ - document.querySelector( - `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.first_name }` - )?.value || '' - } ${ - document.querySelector( - `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.last_name }` - )?.value || '' - }`.trim(); - if ( - ! name && - ( enabledBillingFields[ - SHORTCODE_BILLING_ADDRESS_FIELDS.first_name - ] || - enabledBillingFields[ - SHORTCODE_BILLING_ADDRESS_FIELDS.last_name - ] ) - ) { - return true; + const billingInput = upeElement + ?.closest( 'form.checkout' ) + ?.querySelector( + `[name="${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }"]` + ); + if ( billingInput ) { + // this event only applies to the checkout form, but not "place order" or "add payment method" pages. + $( billingInput ).on( 'change', function () { + togglePaymentMethodForCountry( upeElement ); + } ); + } } - - const billingFieldsToValidate = [ - 'billing_email', - SHORTCODE_BILLING_ADDRESS_FIELDS.country, - SHORTCODE_BILLING_ADDRESS_FIELDS.address_1, - SHORTCODE_BILLING_ADDRESS_FIELDS.city, - SHORTCODE_BILLING_ADDRESS_FIELDS.postcode, - ].filter( ( field ) => enabledBillingFields[ field ] ); - - const country = billingFieldsToValidate.includes( - SHORTCODE_BILLING_ADDRESS_FIELDS.country - ) - ? document.querySelector( - `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }` - )?.value - : null; - - // We need to just find one field with missing information. If even only one is missing, just return early. - return Boolean( - billingFieldsToValidate.find( ( fieldName ) => { - const $field = document.querySelector( `#${ fieldName }` ); - let isRequired = enabledBillingFields[ fieldName ]?.required; - - if ( country && locale && fieldName !== 'billing_email' ) { - const key = fieldName.replace( 'billing_', '' ); - isRequired = - locale[ country ][ key ]?.required ?? - locale.default[ key ]?.required; - } - - const hasValue = $field?.value; - - return isRequired && ! hasValue; - } ) - ); } } ); diff --git a/client/checkout/classic/payment-processing.js b/client/checkout/classic/payment-processing.js index 56595bebf93..cfde44fcb5d 100644 --- a/client/checkout/classic/payment-processing.js +++ b/client/checkout/classic/payment-processing.js @@ -278,7 +278,7 @@ async function createStripePaymentElement( const elements = stripe.elements( options ); const createdStripePaymentElement = elements.create( 'payment', { - ...getUpeSettings(), + ...getUpeSettings( paymentMethodType ), wallets: { applePay: 'never', googlePay: 'never', diff --git a/client/checkout/utils/test/upe.test.js b/client/checkout/utils/test/upe.test.js index c846f832d87..7357840b51a 100644 --- a/client/checkout/utils/test/upe.test.js +++ b/client/checkout/utils/test/upe.test.js @@ -13,7 +13,9 @@ import { dispatchChangeEventFor, togglePaymentMethodForCountry, } from '../upe'; + import { getPaymentMethodsConstants } from '../../constants'; + import { getUPEConfig } from 'wcpay/utils/checkout'; jest.mock( 'wcpay/utils/checkout' ); @@ -27,22 +29,6 @@ jest.mock( '../../constants', () => { describe( 'UPE checkout utils', () => { describe( 'getSelectedUPEGatewayPaymentMethod', () => { let container; - let input; - - beforeAll( () => { - container = document.createElement( 'div' ); - container.innerHTML = ` -
    -
  • - -
  • -
  • - -
  • -
- `; - document.body.appendChild( container ); - } ); beforeEach( () => { getUPEConfig.mockImplementation( ( argument ) => { @@ -54,33 +40,42 @@ describe( 'UPE checkout utils', () => { return 'woocommerce_payments'; } } ); - } ); - afterEach( () => { - input.checked = false; - jest.clearAllMocks(); + // Create container for each test + container = document.createElement( 'div' ); + document.body.appendChild( container ); } ); - afterAll( () => { + afterEach( () => { + // Clean up after each test document.body.removeChild( container ); container = null; + jest.clearAllMocks(); } ); test( 'Selected UPE Payment Method is card', () => { - input = document.querySelector( - '#payment_method_woocommerce_payments' - ); - input.checked = true; - + container.innerHTML = ``; expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'card' ); } ); test( 'Selected UPE Payment Method is bancontact', () => { - input = document.querySelector( - '#payment_method_woocommerce_payments_bancontact' - ); - input.checked = true; - + container.innerHTML = ` + + `; expect( getSelectedUPEGatewayPaymentMethod() ).toBe( 'bancontact' ); } ); } ); @@ -195,10 +190,28 @@ describe( 'UPE checkout utils', () => {
  • - + +
    +
    +
  • - + +
    +
    +
`; @@ -254,24 +267,32 @@ describe( 'UPE checkout utils', () => { } ); it( 'should fall back to card as the default payment method if the selected payment method is toggled off', () => { - const input = document.querySelector( - '#payment_method_woocommerce_payments_bancontact' - ); - input.checked = true; - - const upeElement = document.querySelector( - '.payment_method_woocommerce_payments_bancontact' + const input = document.getElementById( + 'payment_method payment_method_woocommerce_payments_bancontact' ); + input.setAttribute( 'checked', 'checked' ); + + const upeElement = document + .querySelector( + `.wcpay-upe-form[data-payment-method-type="bancontact"]` + ) + .querySelector( '.wcpay-upe-element' ); + const upeContainer = upeElement.closest( '.wc_payment_method' ); document.getElementById( 'billing_country' ).value = 'US'; + const cardPaymentMethod = document + .querySelector( + `.wcpay-upe-form[data-payment-method-type="card"]` + ) + .closest( '.wc_payment_method' ) + .querySelector( + `input[name="payment_method"][value="woocommerce_payments"]` + ); - const cardPaymentMethod = document.querySelector( - '#payment_method_woocommerce_payments' - ); jest.spyOn( cardPaymentMethod, 'click' ); togglePaymentMethodForCountry( upeElement ); - expect( upeElement.style.display ).toBe( 'none' ); + expect( upeContainer.style.display ).toBe( 'none' ); expect( cardPaymentMethod.click ).toHaveBeenCalled(); } ); } ); @@ -312,6 +333,21 @@ describe( 'UPE checkout utils', () => { } ); it( 'should provide terms when cart does not contain subscriptions but the saving checkbox is checked', () => { + const container = document.createElement( 'div' ); + container.innerHTML = ` +
+
+ +
+ `; + document.body.appendChild( container ); + getUPEConfig.mockImplementation( ( argument ) => { if ( argument === 'paymentMethodsConfig' ) { return { @@ -329,9 +365,8 @@ describe( 'UPE checkout utils', () => { createCheckboxElementWhich( true ); - const upeSettings = getUpeSettings(); + const upeSettings = getUpeSettings( 'card' ); - // console.log(result); expect( upeSettings.terms.card ).toEqual( 'always' ); } ); @@ -566,18 +601,41 @@ describe( 'blocksShowLinkButtonHandler', () => { }, }; - beforeEach( () => { + beforeAll( () => { + const wcpayPaymentElement = document.createElement( 'div' ); + wcpayPaymentElement.className = 'wcpay-payment-element'; + + const form = document.createElement( 'form' ); + form.appendChild( wcpayPaymentElement ); + container = document.createElement( 'div' ); container.innerHTML = ` `; - document.body.appendChild( container ); + form.appendChild( container ); + + document.body.appendChild( form ); + } ); + + afterAll( () => { + document.body.innerHTML = ''; + } ); + + beforeEach( () => { + const emailInput = document.getElementById( 'email' ); + if ( emailInput ) { + emailInput.value = ''; + } } ); afterEach( () => { - document.body.removeChild( container ); - container = null; + const stripeLinkButton = document.querySelector( + '.wcpay-stripelink-modal-trigger' + ); + if ( stripeLinkButton ) { + stripeLinkButton.remove(); + } } ); test( 'should hide link button if email input is empty', () => { @@ -595,11 +653,11 @@ describe( 'blocksShowLinkButtonHandler', () => { blocksShowLinkButtonHandler( autofill ); - const stripeLinkButton = document.querySelector( + const linkButton = container.querySelector( '.wcpay-stripelink-modal-trigger' ); - expect( stripeLinkButton ).toBeDefined(); - expect( stripeLinkButton.style.display ).toEqual( 'inline-block' ); + expect( linkButton ).not.toBeNull(); + expect( linkButton.style.display ).toBe( 'inline-block' ); } ); } ); @@ -609,14 +667,18 @@ describe( 'isUsingSavedPaymentMethod', () => { beforeAll( () => { container = document.createElement( 'div' ); container.innerHTML = ` +
- + +
+
+ +
`; document.body.appendChild( container ); } ); diff --git a/client/checkout/utils/upe.js b/client/checkout/utils/upe.js index 761088fa664..500314b9f5b 100644 --- a/client/checkout/utils/upe.js +++ b/client/checkout/utils/upe.js @@ -1,11 +1,16 @@ +/* global wc_address_i18n_params */ + /** * Internal dependencies */ import { getUPEConfig } from 'wcpay/utils/checkout'; -import { getPaymentMethodsConstants } from '../constants'; +import { + getPaymentMethodsConstants, + SHORTCODE_BILLING_ADDRESS_FIELDS, +} from '../constants'; /** - * Generates terms parameter for UPE, with value set for reusable payment methods + * Generates terms for reusable payment methods * * @param {Object} paymentMethodsConfig Object mapping payment method strings to their settings. * @param {string} value The terms value for each available payment method. @@ -25,42 +30,38 @@ export const getTerms = ( paymentMethodsConfig, value = 'always' ) => { }; /** - * Finds selected payment gateway and returns matching Stripe payment method for gateway. + * Returns Stripe payment method (e.g. card, bancontact ) for selected payment gateway. * - * @return {string} Stripe payment method type + * @return {string} Payment method name */ export const getSelectedUPEGatewayPaymentMethod = () => { - const paymentMethodsConfig = getUPEConfig( 'paymentMethodsConfig' ); - const gatewayCardId = getUPEConfig( 'gatewayId' ); - let selectedGatewayId = null; - - // Handle payment method selection on the Checkout page or Add Payment Method page where class names differ. - const radio = document.querySelector( - 'li.wc_payment_method input.input-radio:checked, li.woocommerce-PaymentMethod input.input-radio:checked' + const selectedGateway = document.querySelector( + 'input[name="payment_method"][value*="woocommerce_payments"]:checked' ); - if ( radio !== null ) { - selectedGatewayId = radio.id; - } - if ( selectedGatewayId === 'payment_method_woocommerce_payments' ) { - selectedGatewayId = 'payment_method_woocommerce_payments_card'; + if ( ! selectedGateway ) { + return null; } - let selectedPaymentMethod = null; - - for ( const paymentMethodType in paymentMethodsConfig ) { - if ( - `payment_method_${ gatewayCardId }_${ paymentMethodType }` === - selectedGatewayId - ) { - selectedPaymentMethod = paymentMethodType; - break; - } - } - - return selectedPaymentMethod; + // 'woocommerce_payments_affirm' => 'affirm' + // 'woocommerce_payments_p24' -> 'p24' + // 'woocommerce_payments' -> '' + const paymentMethodType = selectedGateway.value + // non-card elements are prefixed with `woocommerce_payments_*` + .replace( 'woocommerce_payments_', '' ) + // the card element is just called `woocommerce_payments` - we need to account for variation in the name + .replace( 'woocommerce_payments', '' ); + + // if the string is empty, it's the card element + return paymentMethodType || 'card'; }; +/** + * Determines which billing fields should be hidden in the Stripe payment element. + * + * @param {Object} enabledBillingFields Object containing all the billing fields for the WooCommerce checkout. + * @return {Object} Object mapping billing field names to their hidden status. + */ export const getHiddenBillingFields = ( enabledBillingFields ) => { return { name: @@ -83,9 +84,18 @@ export const getHiddenBillingFields = ( enabledBillingFields ) => { }; }; -export const getUpeSettings = () => { +/** + * Generates payment method specific settings object for the Stripe Payment Elements. + * Includes terms visibility, billing fields configuration, and default customer values. + * + * @param {string} paymentMethodType The type of payment method being configured (e.g. card, bancontact) + * @return {Object} Settings object for Payment Elements + */ +export const getUpeSettings = ( paymentMethodType ) => { const upeSettings = {}; - const showTerms = shouldIncludeTerms() ? 'always' : 'never'; + const showTerms = shouldIncludeTerms( paymentMethodType ) + ? 'always' + : 'never'; upeSettings.terms = getTerms( getUPEConfig( 'paymentMethodsConfig' ), @@ -120,22 +130,31 @@ export const getUpeSettings = () => { return upeSettings; }; -function shouldIncludeTerms() { +function getGatewayIdBy( paymentMethodType ) { + const gatewayPrefix = 'woocommerce_payments'; + // Only append underscore and payment method type for non-card payments + return paymentMethodType === 'card' + ? gatewayPrefix + : `${ gatewayPrefix }_${ paymentMethodType }`; +} + +function shouldIncludeTerms( paymentMethodType ) { if ( getUPEConfig( 'cartContainsSubscription' ) ) { return true; } - const savePaymentMethodCheckbox = document.getElementById( - 'wc-woocommerce_payments-new-payment-method' + const paymentsForm = document.querySelector( + `.wcpay-upe-form[data-payment-method-type="${ paymentMethodType }"]` ); - if ( - savePaymentMethodCheckbox !== null && - savePaymentMethodCheckbox.checked - ) { - return true; + if ( ! paymentsForm ) { + return false; } - return false; + const savePaymentMethodCheckbox = paymentsForm.querySelector( + `#wc-${ getGatewayIdBy( paymentMethodType ) }-new-payment-method` + ); + + return savePaymentMethodCheckbox?.checked || false; } export const generateCheckoutEventNames = () => { @@ -183,17 +202,24 @@ export const appendFraudPreventionTokenInputToForm = ( $form ) => { * @return {boolean} Boolean indicating whether a saved payment method is being used. */ export function isUsingSavedPaymentMethod( paymentMethodType ) { - const prefix = '#wc-woocommerce_payments'; - const suffix = '-payment-token-new'; - const savedPaymentSelector = - paymentMethodType === 'card' || paymentMethodType === 'link' - ? prefix + suffix - : prefix + '_' + paymentMethodType + suffix; + const paymentsForm = document.querySelector( + `.wcpay-upe-form[data-payment-method-type="${ paymentMethodType }"]` + ); + if ( ! paymentsForm ) { + return false; + } - return ( - document.querySelector( savedPaymentSelector ) !== null && - ! document.querySelector( savedPaymentSelector ).checked + const newPaymentTokenInputId = `wc-${ getGatewayIdBy( + paymentMethodType + ) }-payment-token-new`; + const newPaymentTokenInput = paymentsForm.querySelector( + `input#${ newPaymentTokenInputId }` ); + if ( ! newPaymentTokenInput ) { + return false; + } + + return ! newPaymentTokenInput.checked; } export function dispatchChangeEventFor( element ) { @@ -279,12 +305,16 @@ export const getPaymentMethodTypes = ( paymentMethodType ) => { }; /** - * Returns the value of the email input on the blocks checkout page. + * Returns the email value from store API. * - * @return {string} The value of email input. + * @return {string} The email value. */ export const getBlocksEmailValue = () => { - return document.getElementById( 'email' ).value; + // .wcpay-payment-element container is rendered only when new payment method is selected + return document + .querySelector( '.wcpay-payment-element' ) + ?.closest( 'form' ) + ?.querySelector( '#email' )?.value; }; /** @@ -293,16 +323,20 @@ export const getBlocksEmailValue = () => { * @param {Object} linkAutofill Stripe Link Autofill instance. */ export const blocksShowLinkButtonHandler = ( linkAutofill ) => { - const emailInput = document.getElementById( 'email' ); + const upeContainer = document.querySelector( '.wcpay-payment-element' ); + if ( ! upeContainer ) return; + + const emailInput = upeContainer + .closest( 'form' ) + ?.querySelector( '#email' ); + if ( ! emailInput ) return; const stripeLinkButton = document.createElement( 'button' ); stripeLinkButton.setAttribute( 'class', 'wcpay-stripelink-modal-trigger' ); stripeLinkButton.style.display = emailInput.value ? 'inline-block' : 'none'; stripeLinkButton.addEventListener( 'click', ( event ) => { event.preventDefault(); - linkAutofill.launch( { - email: document.getElementById( 'email' ).value, - } ); + linkAutofill.launch( { email: emailInput.value } ); } ); emailInput.parentNode.appendChild( stripeLinkButton ); @@ -331,26 +365,98 @@ export const togglePaymentMethodForCountry = ( upeElement ) => { const supportedCountries = paymentMethodsConfig[ paymentMethodType ].countries; const selectedPaymentMethod = getSelectedUPEGatewayPaymentMethod(); + // Simplified approach - find the form ancestor and then search within it + let billingInput = upeElement + ?.closest( 'form.checkout, form#add_payment_method' ) + ?.querySelector( '[name="billing_country"]' ); + + // If not found, fallback to the search in the whole document + if ( ! billingInput ) { + billingInput = document.querySelector( '#billing_country' ); + } /* global wcpayCustomerData */ // in the case of "pay for order", there is no "billing country" input, so we need to rely on backend data. const billingCountry = - document.getElementById( 'billing_country' )?.value || - wcpayCustomerData?.billing_country || - ''; + billingInput?.value || wcpayCustomerData?.billing_country || ''; - const upeContainer = document.querySelector( - '.payment_method_woocommerce_payments_' + paymentMethodType - ); + const upeContainer = upeElement?.closest( '.wc_payment_method' ); if ( supportedCountries.includes( billingCountry ) ) { upeContainer.style.removeProperty( 'display' ); } else { upeContainer.style.display = 'none'; - // if the toggled off payment method was selected, we need to fall back to credit card if ( paymentMethodType === selectedPaymentMethod ) { - document - .querySelector( '#payment_method_woocommerce_payments' ) - .click(); + const cardPaymentForm = document.querySelector( + 'input[name="payment_method"][value="woocommerce_payments"]' + ); + + cardPaymentForm?.click(); } } }; + +function getParsedLocale() { + try { + return JSON.parse( + wc_address_i18n_params.locale.replace( /"/g, '"' ) + ); + } catch ( e ) { + return null; + } +} + +export const isBillingInformationMissing = ( form ) => { + const enabledBillingFields = getUPEConfig( 'enabledBillingFields' ); + + // first name and last name are kinda special - we just need one of them to be at checkout + const name = `${ + form.querySelector( + `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.first_name }` + )?.value || '' + } ${ + form.querySelector( `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.last_name }` ) + ?.value || '' + }`.trim(); + if ( + ! name && + ( enabledBillingFields[ SHORTCODE_BILLING_ADDRESS_FIELDS.first_name ] || + enabledBillingFields[ SHORTCODE_BILLING_ADDRESS_FIELDS.last_name ] ) + ) { + return true; + } + + const billingFieldsToValidate = [ + 'billing_email', + SHORTCODE_BILLING_ADDRESS_FIELDS.country, + SHORTCODE_BILLING_ADDRESS_FIELDS.address_1, + SHORTCODE_BILLING_ADDRESS_FIELDS.city, + SHORTCODE_BILLING_ADDRESS_FIELDS.postcode, + ].filter( ( field ) => enabledBillingFields[ field ] ); + + const country = billingFieldsToValidate.includes( + SHORTCODE_BILLING_ADDRESS_FIELDS.country + ) + ? form.querySelector( `#${ SHORTCODE_BILLING_ADDRESS_FIELDS.country }` ) + ?.value + : null; + + // We need to just find one field with missing information. If even only one is missing, just return early. + return Boolean( + billingFieldsToValidate.find( ( fieldName ) => { + const field = form.querySelector( `#${ fieldName }` ); + let isRequired = enabledBillingFields[ fieldName ]?.required; + const locale = getParsedLocale(); + + if ( country && locale && fieldName !== 'billing_email' ) { + const key = fieldName.replace( 'billing_', '' ); + isRequired = + locale[ country ][ key ]?.required ?? + locale.default[ key ]?.required; + } + + const hasValue = field?.value; + + return isRequired && ! hasValue; + } ) + ); +}; diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index 7dd49c98b67..ee7a161f3b1 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -415,16 +415,23 @@ function () use ( $prepared_customer_data ) { ); } - // Output the form HTML. - if ( ! empty( $this->gateway->get_description() ) ) : ?> -

gateway->get_description() ); ?>

+ ?> +
gateway->get_description() ) ) : + ?> +

gateway->get_description() ); ?>

+ is_test() && false !== $this->gateway->get_payment_method()->get_testing_instructions( $this->account->get_account_country() ) ) : - ?> + if ( WC_Payments::mode()->is_test() && false !== $this->gateway->get_payment_method()->get_testing_instructions( $this->account->get_account_country() ) ) : + ?>

- '

gateway->id ); diff --git a/tests/unit/test-class-wc-payments-checkout.php b/tests/unit/test-class-wc-payments-checkout.php index 962c7bc4d8b..1fcbe1093ff 100644 --- a/tests/unit/test-class-wc-payments-checkout.php +++ b/tests/unit/test-class-wc-payments-checkout.php @@ -124,7 +124,7 @@ public function set_up() { // Use a callback to suppresses the output buffering being printed to the CLI. $this->setOutputCallback( function ( $output ) { - preg_match_all( '/.*.*/s', $output ); + preg_match_all( '/.*.*/s', $output ); } ); From 3bce9402da6209fe569725d981e998c7d8116935 Mon Sep 17 00:00:00 2001 From: Daniel Mallory Date: Thu, 5 Dec 2024 11:00:02 +0000 Subject: [PATCH 16/83] Fixes issue with QIT authentication parsing (#9755) Co-authored-by: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com> --- changelog/dev-qit-auth-fix-take-2 | 4 ++++ tests/qit/common.sh | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog/dev-qit-auth-fix-take-2 diff --git a/changelog/dev-qit-auth-fix-take-2 b/changelog/dev-qit-auth-fix-take-2 new file mode 100644 index 00000000000..67ec99abd79 --- /dev/null +++ b/changelog/dev-qit-auth-fix-take-2 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. diff --git a/tests/qit/common.sh b/tests/qit/common.sh index c95ef0ed25a..21c3ac3bc51 100644 --- a/tests/qit/common.sh +++ b/tests/qit/common.sh @@ -26,7 +26,7 @@ QIT_BINARY=${QIT_BINARY:-./vendor/bin/qit} # Add the partner by validating credentials. if ! $QIT_BINARY list | grep -q 'partner:remove'; then echo "Adding partner with QIT credentials..." - $QIT_BINARY partner:add --user=$QIT_USER --application_password=$QIT_PASSWORD + $QIT_BINARY partner:add --user=$QIT_USER --application_password="$QIT_PASSWORD" if [ $? -ne 0 ]; then echo "Failed to add partner. Exiting with status 1." exit 1 From c9ebf9382dddc40d0cd5b5ce61c129a17a120c34 Mon Sep 17 00:00:00 2001 From: Leonardo Lopes de Albuquerque Date: Thu, 5 Dec 2024 13:50:23 -0300 Subject: [PATCH 17/83] Add WooPay clickwrap support (#9752) --- .../add-2253-clickwrap-terms-and-conditions | 4 ++ .../test/woopay-settings.test.js | 2 +- .../woopay-settings.js | 2 +- includes/woopay/class-woopay-session.php | 54 +++++++++++++++---- 4 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 changelog/add-2253-clickwrap-terms-and-conditions diff --git a/changelog/add-2253-clickwrap-terms-and-conditions b/changelog/add-2253-clickwrap-terms-and-conditions new file mode 100644 index 00000000000..ac0a4ece4b7 --- /dev/null +++ b/changelog/add-2253-clickwrap-terms-and-conditions @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Clickwrap terms and conditions support on WooPay diff --git a/client/settings/express-checkout-settings/test/woopay-settings.test.js b/client/settings/express-checkout-settings/test/woopay-settings.test.js index d7ce1018146..abcac0754ce 100644 --- a/client/settings/express-checkout-settings/test/woopay-settings.test.js +++ b/client/settings/express-checkout-settings/test/woopay-settings.test.js @@ -149,7 +149,7 @@ describe( 'WooPaySettings', () => { // confirm settings headings expect( screen.queryByRole( 'heading', { - name: 'Policies and custom text', + name: 'Checkout policies', } ) ).toBeInTheDocument(); diff --git a/client/settings/express-checkout-settings/woopay-settings.js b/client/settings/express-checkout-settings/woopay-settings.js index 0ba3f8f7b92..f2a4931f9f6 100644 --- a/client/settings/express-checkout-settings/woopay-settings.js +++ b/client/settings/express-checkout-settings/woopay-settings.js @@ -255,7 +255,7 @@ const WooPaySettings = ( { section } ) => {

{ __( - 'Policies and custom text', + 'Checkout policies', 'woocommerce-payments' ) }

diff --git a/includes/woopay/class-woopay-session.php b/includes/woopay/class-woopay-session.php index dce1878265e..5c799ee0ead 100644 --- a/includes/woopay/class-woopay-session.php +++ b/includes/woopay/class-woopay-session.php @@ -898,9 +898,10 @@ private static function get_formatted_custom_message() { */ private static function get_option_fields_status() { // Shortcode checkout options. - $company = get_option( 'woocommerce_checkout_company_field', 'optional' ); - $address_2 = get_option( 'woocommerce_checkout_address_2_field', 'optional' ); - $phone = get_option( 'woocommerce_checkout_phone_field', 'required' ); + $company = get_option( 'woocommerce_checkout_company_field', 'optional' ); + $address_2 = get_option( 'woocommerce_checkout_address_2_field', 'optional' ); + $phone = get_option( 'woocommerce_checkout_phone_field', 'required' ); + $terms_checkbox = ! empty( get_option( 'woocommerce_terms_page_id', null ) ); // Blocks checkout options. To get the blocks checkout options, we need // to parse the checkout page content because the options are stored @@ -910,9 +911,10 @@ private static function get_option_fields_status() { if ( empty( $checkout_page ) ) { return [ - 'company' => $company, - 'address_2' => $address_2, - 'phone' => $phone, + 'company' => $company, + 'address_2' => $address_2, + 'phone' => $phone, + 'terms_checkbox' => $terms_checkbox, ]; } @@ -947,12 +949,46 @@ private static function get_option_fields_status() { if ( isset( $checkout_block_attrs['showPhoneField'] ) && false === $checkout_block_attrs['showPhoneField'] ) { $phone = 'hidden'; } + + $fields_block = self::get_inner_block( $checkout_page_blocks[ $checkout_block_index ], 'woocommerce/checkout-fields-block' ); + $terms_block = self::get_inner_block( $fields_block, 'woocommerce/checkout-terms-block' ); + $terms_checkbox = isset( $terms_block['attrs']['checkbox'] ) && $terms_block['attrs']['checkbox']; } return [ - 'company' => $company, - 'address_2' => $address_2, - 'phone' => $phone, + 'company' => $company, + 'address_2' => $address_2, + 'phone' => $phone, + 'terms_checkbox' => $terms_checkbox, ]; } + + /** + * Searches for an inner block with the given name. + * + * @param array $current_block A block that contains child blocks. + * @param string $inner_block_name The name of a child block. + * @return array|null + */ + private static function get_inner_block( $current_block, $inner_block_name ) { + + if ( ! isset( $current_block['innerBlocks'] ) ) { + return; + } + + $inner_block_index = array_search( + $inner_block_name, + array_column( + $current_block['innerBlocks'], + 'blockName' + ), + true + ); + + if ( ! isset( $current_block['innerBlocks'][ $inner_block_index ] ) ) { + return; + } + + return $current_block['innerBlocks'][ $inner_block_index ]; + } } From 907c23822c8b66d797f4323ee9f03a3d0e24002f Mon Sep 17 00:00:00 2001 From: Mike Moore Date: Thu, 5 Dec 2024 14:45:36 -0500 Subject: [PATCH 18/83] Fallback to card payment type in get_payment_method_types (#9829) --- changelog/8969-fallback-to-card-payment-type | 5 ++ includes/class-wc-payment-gateway-wcpay.php | 6 +-- .../test-class-upe-split-payment-gateway.php | 53 ------------------- .../test-class-wc-payment-gateway-wcpay.php | 32 +++++------ 4 files changed, 20 insertions(+), 76 deletions(-) create mode 100644 changelog/8969-fallback-to-card-payment-type diff --git a/changelog/8969-fallback-to-card-payment-type b/changelog/8969-fallback-to-card-payment-type new file mode 100644 index 00000000000..ee66dbfa7e7 --- /dev/null +++ b/changelog/8969-fallback-to-card-payment-type @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: Small change to payment method types fallback scenario. + + diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 02d26bbcb4c..3623aded21a 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -1571,7 +1571,6 @@ public function process_payment_for_order( $cart, $payment_information, $schedul throw new Exception( WC_Payments_Utils::get_filtered_error_message( $e ) ); } - $payment_methods = $this->get_payment_method_types( $payment_information ); // The sanitize_user call here is deliberate: it seems the most appropriate sanitization function // for a string that will only contain latin alphanumeric characters and underscores. // phpcs:ignore WordPress.Security.NonceVerification.Missing @@ -1602,6 +1601,8 @@ public function process_payment_for_order( $cart, $payment_information, $schedul } if ( empty( $intent ) ) { + $payment_methods = $this->get_payment_method_types( $payment_information ); + $request = Create_And_Confirm_Intention::create(); $request->set_amount( $converted_amount ); $request->set_currency_code( $currency ); @@ -2126,9 +2127,6 @@ public function get_payment_method_types( $payment_information ): array { $order = $payment_information->get_order(); $order_id = $order instanceof WC_Order ? $order->get_id() : null; $payment_methods = $this->get_payment_methods_from_gateway_id( $token->get_gateway_id(), $order_id ); - } else { - // Final fallback case, if all else fails. - $payment_methods = WC_Payments::get_gateway()->get_payment_method_ids_enabled_at_checkout( null, true ); } return $payment_methods; diff --git a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php index 9d6185387f6..a3284a840ce 100644 --- a/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php +++ b/tests/unit/payment-methods/test-class-upe-split-payment-gateway.php @@ -1140,59 +1140,6 @@ public function test_get_payment_methods_without_request_context() { $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); } - /** - * Test get_payment_method_types without post request context or saved token. - * - * @return void - */ - public function test_get_payment_methods_without_request_context_or_token() { - $mock_upe_gateway = $this->getMockBuilder( WC_Payment_Gateway_WCPay::class ) - ->setConstructorArgs( - [ - $this->mock_api_client, - $this->mock_wcpay_account, - $this->mock_customer_service, - $this->mock_token_service, - $this->mock_action_scheduler_service, - $this->mock_payment_methods[ Payment_Method::CARD ], - $this->mock_payment_methods, - $this->order_service, - $this->mock_dpps, - $this->mock_localization_service, - $this->mock_fraud_service, - $this->mock_duplicates_detection_service, - $this->mock_rate_limiter, - ] - ) - ->setMethods( - [ - 'get_payment_methods_from_gateway_id', - 'get_payment_method_ids_enabled_at_checkout', - ] - ) - ->getMock(); - - $payment_information = new Payment_Information( 'pm_mock' ); - - unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification - - $gateway = WC_Payments::get_gateway(); - WC_Payments::set_gateway( $mock_upe_gateway ); - - $mock_upe_gateway->expects( $this->never() ) - ->method( 'get_payment_methods_from_gateway_id' ); - - $mock_upe_gateway->expects( $this->once() ) - ->method( 'get_payment_method_ids_enabled_at_checkout' ) - ->willReturn( [ Payment_Method::CARD ] ); - - $payment_methods = $mock_upe_gateway->get_payment_method_types( $payment_information ); - - $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); - - WC_Payments::set_gateway( $gateway ); - } - /** * Test get_payment_methods_from_gateway_id function with UPE enabled. * diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 4b7bf857997..6da6fbc85c0 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -1239,21 +1239,6 @@ public function test_get_payment_methods_without_post_request_context() { $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); } - public function test_get_payment_methods_without_request_context_or_token() { - $payment_information = new Payment_Information( 'pm_mock' ); - - unset( $_POST['payment_method'] ); // phpcs:ignore WordPress.Security.NonceVerification - - $gateway = WC_Payments::get_gateway(); - WC_Payments::set_gateway( $this->card_gateway ); - - $payment_methods = $this->card_gateway->get_payment_method_types( $payment_information ); - - $this->assertSame( [ Payment_Method::CARD ], $payment_methods ); - - WC_Payments::set_gateway( $gateway ); - } - public function test_get_payment_methods_from_gateway_id_upe() { WC_Helper_Order::create_order(); @@ -2542,7 +2527,7 @@ public function test_process_payment_for_order_not_from_request() { $order->add_payment_token( $token ); $order->save(); - $pi = new Payment_Information( 'pm_test', $order, null, null, null, null, null, '', 'card' ); + $pi = new Payment_Information( 'pm_test', $order, null, $token, null, null, null, '', 'card' ); $request = $this->mock_wcpay_request( Create_And_Confirm_Intention::class ); $request->expects( $this->once() ) @@ -3086,7 +3071,10 @@ public function test_process_payment_caches_mimimum_amount_and_displays_error_up ->method( 'get_customer_id_by_user_id' ) ->will( $this->returnValue( $customer ) ); - $_POST = [ 'wcpay-payment-method' => $pm = 'pm_mock' ]; + $_POST = [ + 'wcpay-payment-method' => $pm = 'pm_mock', + 'payment_method' => 'woocommerce_payments', + ]; $this->get_fraud_prevention_service_mock() ->expects( $this->once() ) @@ -3922,7 +3910,10 @@ public function test_process_payment_rate_limiter_enabled_throw_exception() { public function test_process_payment_returns_correct_redirect() { $order = WC_Helper_Order::create_order(); - $_POST = [ 'wcpay-payment-method' => 'pm_mock' ]; + $_POST = [ + 'wcpay-payment-method' => 'pm_mock', + 'payment_method' => 'woocommerce_payments', + ]; $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1 ) ->expects( $this->once() ) @@ -3945,7 +3936,10 @@ public function test_process_payment_returns_correct_redirect() { public function test_process_payment_returns_correct_redirect_when_using_payment_request() { $order = WC_Helper_Order::create_order(); $_POST['payment_request_type'] = 'google_pay'; - $_POST = [ 'wcpay-payment-method' => 'pm_mock' ]; + $_POST = [ + 'wcpay-payment-method' => 'pm_mock', + 'payment_method' => 'woocommerce_payments', + ]; $this->mock_wcpay_request( Create_And_Confirm_Intention::class, 1 ) ->expects( $this->once() ) From 007d6d6da6a665dc991a04657d08cf9a098418d5 Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 6 Dec 2024 12:39:20 +0100 Subject: [PATCH 19/83] fix: undefined `$cart_contains_subscription` (#9893) --- changelog/frosso-patch-1 | 4 ++++ includes/class-wc-payments.php | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changelog/frosso-patch-1 diff --git a/changelog/frosso-patch-1 b/changelog/frosso-patch-1 new file mode 100644 index 00000000000..e3812625698 --- /dev/null +++ b/changelog/frosso-patch-1 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +fix: undefined $cart_contains_subscription diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 103ead1e52d..17300478794 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -1876,12 +1876,13 @@ public static function init_woopay() { public static function load_stripe_bnpl_site_messaging() { // The messaging element shall not be shown for subscription products. // As we are not too deep into subscriptions API, we follow simplistic approach for now. - $is_subscription = false; - $are_subscriptions_enabled = class_exists( 'WC_Subscriptions' ) || class_exists( 'WC_Subscriptions_Core_Plugin' ); + $is_subscription = false; + $cart_contains_subscription = false; + $are_subscriptions_enabled = class_exists( 'WC_Subscriptions' ) || class_exists( 'WC_Subscriptions_Core_Plugin' ); if ( $are_subscriptions_enabled ) { - global $product; - $is_subscription = $product && WC_Subscriptions_Product::is_subscription( $product ); - $cart_contains_subscription = is_cart() && WC_Subscriptions_Cart::cart_contains_subscription(); + global $product; + $is_subscription = $product && WC_Subscriptions_Product::is_subscription( $product ); + $cart_contains_subscription = is_cart() && WC_Subscriptions_Cart::cart_contains_subscription(); } if ( ! $is_subscription && ! $cart_contains_subscription ) { From 81ee27e065caf71a4d8e48ce2b69225d635607da Mon Sep 17 00:00:00 2001 From: Dan Paun <82826872+dpaun1985@users.noreply.github.com> Date: Fri, 6 Dec 2024 14:52:26 +0200 Subject: [PATCH 20/83] Implement gateway method to retrieve recommended PMs (#9825) Co-authored-by: Dan Paun Co-authored-by: Vlad Olaru Co-authored-by: Vlad Olaru --- changelog/add-9690-recommended-pm | 4 + includes/class-database-cache.php | 1 + includes/class-wc-payment-gateway-wcpay.php | 31 ++++++- includes/class-wc-payments-account.php | 63 ++++++++++++++ .../class-wc-payments-onboarding-service.php | 29 +++++++ .../class-wc-payments-api-client.php | 60 +++++++++++++ .../test-class-wc-payment-gateway-wcpay.php | 51 +++++++++++ tests/unit/test-class-wc-payments-account.php | 85 +++++++++++++++++++ 8 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 changelog/add-9690-recommended-pm diff --git a/changelog/add-9690-recommended-pm b/changelog/add-9690-recommended-pm new file mode 100644 index 00000000000..2d615350daa --- /dev/null +++ b/changelog/add-9690-recommended-pm @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Implement gateway method to retrieve recommended payment method. diff --git a/includes/class-database-cache.php b/includes/class-database-cache.php index 1e285be59ab..e29bdbfc374 100644 --- a/includes/class-database-cache.php +++ b/includes/class-database-cache.php @@ -20,6 +20,7 @@ class Database_Cache implements MultiCurrencyCacheInterface { const BUSINESS_TYPES_KEY = 'wcpay_business_types_data'; const PAYMENT_PROCESS_FACTORS_KEY = 'wcpay_payment_process_factors'; const FRAUD_SERVICES_KEY = 'wcpay_fraud_services_data'; + const RECOMMENDED_PAYMENT_METHODS = 'wcpay_recommended_payment_methods'; /** * Refresh during AJAX calls is avoided, but white-listing diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 3623aded21a..0f53c3dde42 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -4498,11 +4498,40 @@ public function find_duplicates() { return $this->duplicate_payment_methods_detection_service->find_duplicates(); } + /** + * Get the recommended payment methods list. + * + * @param string $country_code Optional. The business location country code. Provide a 2-letter ISO country code. + * If not provided, the account country will be used if the account is connected. + * Otherwise, the store's base country will be used. + * + * @return array List of recommended payment methods for the given country. + * Empty array if there are no recommendations available. + * Each item in the array should be an associative array with at least the following entries: + * - @string id: The payment method ID. + * - @string title: The payment method title/name. + * - @bool enabled: Whether the payment method is enabled. + * - @int order/priority: The order/priority of the payment method. + */ + public function get_recommended_payment_methods( string $country_code = '' ): array { + if ( empty( $country_code ) ) { + // If the account is connected, use the account country. + if ( $this->account->is_provider_connected() ) { + $country_code = $this->get_account_country(); + } else { + // If the account is not connected, use the store's base country. + $country_code = WC()->countries->get_base_country(); + } + } + + return $this->account->get_recommended_payment_methods( $country_code ); + } + /** * Determine whether redirection is needed for the non-card UPE payment method. * * @param array $payment_methods The list of payment methods used for the order processing, usually consists of one method only. - * @return boolean True if the arrray consist of only one payment method which is not a card. False otherwise. + * @return boolean True if the array consist of only one payment method which is not a card. False otherwise. */ private function upe_needs_redirection( $payment_methods ) { return 1 === count( $payment_methods ) && 'card' !== $payment_methods[0]; diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index e884d582ac8..85b0f336263 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -665,6 +665,69 @@ public function get_supported_countries(): array { return WC_Payments_Utils::supported_countries(); } + /** + * Get the account recommended payment methods to use during onboarding. + * + * @param string $country_code The account's business location country code. Provide a 2-letter ISO country code. + * + * @return array List of recommended payment methods for the given country. + * Empty array if there are no recommendations, we failed to retrieve recommendations, + * or the country is not supported by WooPayments. + */ + public function get_recommended_payment_methods( string $country_code ): array { + // Return early if the country is not supported. + if ( ! array_key_exists( $country_code, $this->get_supported_countries() ) ) { + return []; + } + + // We use the locale for the current user (defaults to the site locale). + $recommended_pms = $this->onboarding_service->get_recommended_payment_methods( $country_code, get_user_locale() ); + $recommended_pms = is_array( $recommended_pms ) ? array_values( $recommended_pms ) : []; + + // Validate the recommended payment methods. + // Each must have an ID and a title. + $recommended_pms = array_filter( + $recommended_pms, + function ( $pm ) { + return isset( $pm['id'] ) && isset( $pm['title'] ); + } + ); + + // Standardize/normalize. + // Determine if the payment method should be recommended as enabled. + $recommended_pms = array_map( + function ( $pm ) { + if ( ! isset( $pm['enabled'] ) ) { + // Default to enabled since this is a recommended list. + $pm['enabled'] = true; + // Look at the type, if available, to determine if it should be enabled. + if ( isset( $pm['type'] ) ) { + $pm['enabled'] = 'available' !== $pm['type']; + } + } + + return $pm; + }, + $recommended_pms + ); + // Fill in the priority entries with a fallback to the index of the recommendation in the list. + $recommended_pms = array_map( + function ( $pm, $index ) { + if ( ! isset( $pm['priority'] ) ) { + $pm['priority'] = $index; + } else { + $pm['priority'] = intval( $pm['priority'] ); + } + + return $pm; + }, + $recommended_pms, + array_keys( $recommended_pms ) + ); + + return $recommended_pms; + } + /** * Gets the account live mode value. * diff --git a/includes/class-wc-payments-onboarding-service.php b/includes/class-wc-payments-onboarding-service.php index 8700fa4fa29..bce87bcbf0c 100644 --- a/includes/class-wc-payments-onboarding-service.php +++ b/includes/class-wc-payments-onboarding-service.php @@ -150,6 +150,35 @@ function () use ( $locale ) { ); } + /** + * Retrieve and cache the account recommended payment methods list. + * + * @param string $country_code The account's business location country code. Provide a 2-letter ISO country code. + * @param string $locale Optional. The locale to use to i18n the data. + * + * @return ?array The recommended payment methods list. + * NULL on retrieval or validation error. + */ + public function get_recommended_payment_methods( string $country_code, string $locale = '' ): ?array { + $cache_key = Database_Cache::RECOMMENDED_PAYMENT_METHODS . '__' . $country_code; + if ( ! empty( $locale ) ) { + $cache_key .= '__' . $locale; + } + + return \WC_Payments::get_database_cache()->get_or_add( + $cache_key, + function () use ( $country_code, $locale ) { + try { + return $this->payments_api_client->get_recommended_payment_methods( $country_code, $locale ); + } catch ( API_Exception $e ) { + // Return NULL to signal retrieval error. + return null; + } + }, + 'is_array' + ); + } + /** * Retrieve the embedded KYC session and handle initial account creation (if necessary). * 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 01c57eb8969..b3adf5bf7eb 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -81,6 +81,7 @@ class WC_Payments_API_Client implements MultiCurrencyApiClientInterface { const FRAUD_RULESET_API = 'fraud_ruleset'; const COMPATIBILITY_API = 'compatibility'; const REPORTING_API = 'reporting/payment_activity'; + const RECOMMENDED_PAYMENT_METHODS = 'payment_methods/recommended'; /** * Common keys in API requests/responses that we might want to redact. @@ -453,6 +454,65 @@ public function get_transactions_export( $filters = [], $user_email = '', $depos return $this->request( $filters, self::TRANSACTIONS_API . '/download', self::POST ); } + /** + * Fetch account recommended payment methods data for a given country. + * + * @param string $country_code The account's business location country code. Provide a 2-letter ISO country code. + * @param string $locale Optional. The locale to instruct the platform to use for i18n. + * + * @return array The recommended payment methods data. + * @throws API_Exception Exception thrown on request failure. + */ + public function get_recommended_payment_methods( string $country_code, string $locale = '' ): array { + // We can't use the request method here because this route doesn't require a connected store + // and we request this data pre-onboarding. + // By this point, we have an expired transient or the store context has changed. + // Query for incentives by calling the WooPayments API. + $url = add_query_arg( + [ + 'country_code' => $country_code, + 'locale' => $locale, + ], + self::ENDPOINT_BASE . '/' . self::ENDPOINT_REST_BASE . '/' . self::RECOMMENDED_PAYMENT_METHODS, + ); + + $response = wp_remote_get( + $url, + [ + 'headers' => apply_filters( + 'wcpay_api_request_headers', + [ + 'Content-type' => 'application/json; charset=utf-8', + ] + ), + 'user-agent' => $this->user_agent, + 'timeout' => self::API_TIMEOUT_SECONDS, + 'sslverify' => false, + ] + ); + + if ( is_wp_error( $response ) ) { + Logger::error( 'HTTP_REQUEST_ERROR ' . var_export( $response, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export + $message = sprintf( + // translators: %1: original error message. + __( 'Http request failed. Reason: %1$s', 'woocommerce-payments' ), + $response->get_error_message() + ); + throw new API_Exception( $message, 'wcpay_http_request_failed', 500 ); + } + + $results = []; + if ( 200 === wp_remote_retrieve_response_code( $response ) ) { + // Decode the results, falling back to an empty array. + $results = $this->extract_response_body( $response ); + if ( ! is_array( $results ) ) { + $results = []; + } + } + + return $results; + } + /** * Fetch a single transaction with provided id. * diff --git a/tests/unit/test-class-wc-payment-gateway-wcpay.php b/tests/unit/test-class-wc-payment-gateway-wcpay.php index 6da6fbc85c0..1827041a1fc 100644 --- a/tests/unit/test-class-wc-payment-gateway-wcpay.php +++ b/tests/unit/test-class-wc-payment-gateway-wcpay.php @@ -3963,6 +3963,57 @@ public function is_proper_intent_used_with_order_returns_false() { $this->assertFalse( $this->card_gateway->is_proper_intent_used_with_order( WC_Helper_Order::create_order(), 'wrong_intent_id' ) ); } + public function test_get_recommended_payment_method() { + $this->mock_wcpay_account + ->expects( $this->once() ) + ->method( 'get_recommended_payment_methods' ) + ->with( 'US' ); + $this->card_gateway->get_recommended_payment_methods( 'US' ); + } + + public function get_recommended_payment_method_no_country_code_provider() { + return [ + 'provider connected' => [ true, 'test' ], + 'provider not connected' => [ false, 'US' ], + ]; + } + + /** + * @dataProvider get_recommended_payment_method_no_country_code_provider + */ + public function test_get_recommended_payment_method_no_country_code_provided( $is_provider_connected, $country_code ) { + // Set base country fallback to US. + $filter_callback = function () { + return 'US'; + }; + add_filter( 'woocommerce_countries_base_country', $filter_callback ); + + $this->mock_wcpay_account + ->expects( $this->once() ) + ->method( 'is_provider_connected' ) + ->willReturn( $is_provider_connected ); + + $this->mock_wcpay_account + ->expects( $this->any() ) + ->method( 'is_stripe_connected' ) + ->willReturn( true ); + + $this->mock_wcpay_account + ->expects( $this->any() ) + ->method( 'get_account_country' ) + ->willReturn( $country_code ); + + $this->mock_wcpay_account + ->expects( $this->once() ) + ->method( 'get_recommended_payment_methods' ) + ->with( $country_code ); + + $this->assertSame( [], $this->card_gateway->get_recommended_payment_methods( '' ) ); + + // Clean up. + remove_filter( 'woocommerce_countries_base_country', $filter_callback ); + } + /** * Sets up the expectation for a certain factor for the new payment * process to be either set or unset. diff --git a/tests/unit/test-class-wc-payments-account.php b/tests/unit/test-class-wc-payments-account.php index f0087e3e966..934a7a72ada 100644 --- a/tests/unit/test-class-wc-payments-account.php +++ b/tests/unit/test-class-wc-payments-account.php @@ -3174,6 +3174,91 @@ public function test_get_tracking_info() { $this->assertSame( $expected, $this->wcpay_account->get_tracking_info() ); } + public function test_get_recommended_payment_methods_unsupported_country() { + $this->assertSame( [], $this->wcpay_account->get_recommended_payment_methods( 'XZ' ) ); + } + + public function get_recommended_payment_methods_provider() { + return [ + 'No PMs suggested' => [ 'US', [], [] ], + 'Invalid PMs array' => [ + 'US', + [ + 'type' => 'available', + 'enabled' => false, + ], + [], + ], + 'Enabled flag and priority not set' => [ + 'US', + [ + [ + 'id' => 1, + 'title' => 'test PM', + 'type' => 'available', + ], + [ + 'id' => 2, + 'title' => 'test PM 2', + 'type' => 'available', + ], + ], + [ + [ + 'id' => 1, + 'title' => 'test PM', + 'type' => 'available', + 'enabled' => false, + 'priority' => 0, + ], + [ + 'id' => 2, + 'title' => 'test PM 2', + 'type' => 'available', + 'enabled' => false, + 'priority' => 1, + ], + ], + ], + 'Enabled flag and priority set' => [ + 'US', + [ + [ + 'id' => 1, + 'title' => 'test PM', + 'type' => 'available', + 'enabled' => true, + 'priority' => 1, + ], + ], + [ + [ + 'id' => 1, + 'title' => 'test PM', + 'type' => 'available', + 'enabled' => true, + 'priority' => 1, + ], + ], + ], + ]; + } + + /** + * @dataProvider get_recommended_payment_methods_provider + */ + public function test_get_recommended_payment_methods( $country_code, $recommended_pms, $expected ) { + + $this->mock_empty_cache(); + $this->mock_onboarding_service + ->expects( $this->once() ) + ->method( 'get_recommended_payment_methods' ) + ->with( $country_code ) + ->willReturn( $recommended_pms ); + + $this->assertSame( $expected, $this->wcpay_account->get_recommended_payment_methods( $country_code ) ); + } + /** * Sets up the mocked cache to simulate that its empty and call the generator. */ From a842ba4e986e4e141feb7e4d1f8ba53b486fd750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3fer=20Reykjal=C3=ADn?= <13835680+reykjalin@users.noreply.github.com> Date: Fri, 6 Dec 2024 13:54:15 -0500 Subject: [PATCH 21/83] Fix rounding error with deposit products (#9876) --- .../fix-rounding-error-with-deposit-products | 4 ++ includes/multi-currency/MultiCurrency.php | 38 +++++++++++++++++++ .../test-class-multi-currency.php | 17 +++++---- 3 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 changelog/fix-rounding-error-with-deposit-products diff --git a/changelog/fix-rounding-error-with-deposit-products b/changelog/fix-rounding-error-with-deposit-products new file mode 100644 index 00000000000..d42215e3919 --- /dev/null +++ b/changelog/fix-rounding-error-with-deposit-products @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. diff --git a/includes/multi-currency/MultiCurrency.php b/includes/multi-currency/MultiCurrency.php index 301503d9ca0..9ab1ac0f19a 100644 --- a/includes/multi-currency/MultiCurrency.php +++ b/includes/multi-currency/MultiCurrency.php @@ -832,7 +832,12 @@ public function get_price( $price, string $type ): float { return (float) $price; } + // We must ceil the converted price here so that we don't introduce rounding errors when + // summing up costs. Consider, e.g. a converted price of 10.003 for a 2-decimal currency. + // A single product would cost 10.00, but 2 of them would cost 20.01, _unless_ we round + // the individual parts correctly. $converted_price = ( (float) $price ) * $currency->get_rate(); + $converted_price = $this->ceil_price_for_currency( $converted_price, $currency ); if ( 'tax' === $type || 'coupon' === $type || 'exchange_rate' === $type ) { return $converted_price; @@ -1356,6 +1361,39 @@ protected function ceil_price( float $price, float $rounding ): float { return ceil( $price / $rounding ) * $rounding; } + /** + * Ceils the price to the precision dictated by the number of decimals in the provided currency. + * + * For example: US$10.0091 -> US$10.01, JPY 1001.01 -> JPY 1002. + * + * @param float $price The price to be ceiled. + * @param Currency $currency The currency used to figure out the ceil precision. + * + * @return float The ceiled price. + */ + protected function ceil_price_for_currency( float $price, Currency $currency ): float { + // phpcs:disable Squiz.PHP.CommentedOutCode.Found, example comments look like code. + + // Example to explain the math: + // $price = 10.003. + // expected rounding = 10.01. + + // $num_decimals = 2. + // $factor. = 10^2 = 100. + $num_decimals = absint( + $this->localization_service->get_currency_format( + $currency->get_code() + )['num_decimals'] + ); + $factor = 10 ** $num_decimals; // 10^{$num_decimals}. + + // ceil( 10.003 * $factor ) = ceil( 1_000.3 ) = 1_001. + // 1_001 / 100 = 10.01. + return ceil( $price * $factor ) / $factor; // = 10.01. + + // phpcs:enable Squiz.PHP.CommentedOutCode.Found + } + /** * Sets up the available currencies, which are alphabetical by name. * diff --git a/tests/unit/multi-currency/test-class-multi-currency.php b/tests/unit/multi-currency/test-class-multi-currency.php index 6d4eddaab84..a5a254ed7ec 100644 --- a/tests/unit/multi-currency/test-class-multi-currency.php +++ b/tests/unit/multi-currency/test-class-multi-currency.php @@ -620,24 +620,27 @@ public function test_get_price_returns_converted_coupon_price_without_adjustment WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, 'GBP' ); add_filter( 'wcpay_multi_currency_apply_charm_only_to_products', '__return_false' ); - // 0.708099 * 10 = 7,08099 - $this->assertSame( 7.08099, $this->multi_currency->get_price( '10.0', 'coupon' ) ); + // 0.708099 * 10 = 7.08099. + // ceil( 7.08099, 2 ) = 7.09. + $this->assertSame( 7.09, $this->multi_currency->get_price( '10.0', 'coupon' ) ); } public function test_get_price_returns_converted_exchange_rate_without_adjustments() { WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, 'GBP' ); add_filter( 'wcpay_multi_currency_apply_charm_only_to_products', '__return_false' ); - // 0.708099 * 10 = 7,08099 - $this->assertSame( 7.08099, $this->multi_currency->get_price( '10.0', 'exchange_rate' ) ); + // 0.708099 * 10 = 7.08099. + // ceil( 7.08099, 2 ) = 7.09. + $this->assertSame( 7.09, $this->multi_currency->get_price( '10.0', 'exchange_rate' ) ); } public function test_get_price_returns_converted_tax_price() { WC()->session->set( WCPay\MultiCurrency\MultiCurrency::CURRENCY_SESSION_KEY, 'GBP' ); add_filter( 'wcpay_multi_currency_apply_charm_only_to_products', '__return_false' ); - // 0.708099 * 10 = 7,08099 - $this->assertSame( 7.08099, $this->multi_currency->get_price( '10.0', 'tax' ) ); + // 0.708099 * 10 = 7.08099. + // ceil( 7.08099, 2 ) = 7.09. + $this->assertSame( 7.09, $this->multi_currency->get_price( '10.0', 'tax' ) ); } /** @@ -1014,7 +1017,7 @@ public function test_set_new_customer_currency_meta_does_not_update_user_meta_if public function get_price_provider() { return [ - [ '5.2499', '0.00', 5.2499 ], + [ '5.2499', '0.00', 5.25 ], // Even though the precision is 0.00 we make sure the amount is ceiled to the currency's number of digits. [ '5.2499', '0.25', 5.25 ], [ '5.2500', '0.25', 5.25 ], [ '5.2501', '0.25', 5.50 ], From c241ed64199704a1aa4cbe9612264f09088ef341 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 6 Dec 2024 14:14:29 -0500 Subject: [PATCH 22/83] Normalize HK addresses for Apple Pay (#9869) --- changelog/as-hk-address | 4 + ...lass-express-checkout-hong-kong-states.php | 360 ++++++++++++++++++ ...payments-express-checkout-ajax-handler.php | 4 +- ...ayments-express-checkout-button-helper.php | 40 ++ 4 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 changelog/as-hk-address create mode 100644 includes/constants/class-express-checkout-hong-kong-states.php diff --git a/changelog/as-hk-address b/changelog/as-hk-address new file mode 100644 index 00000000000..d58ddb9ffd9 --- /dev/null +++ b/changelog/as-hk-address @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Normalize HK addresses for ECE diff --git a/includes/constants/class-express-checkout-hong-kong-states.php b/includes/constants/class-express-checkout-hong-kong-states.php new file mode 100644 index 00000000000..cd7154eeca0 --- /dev/null +++ b/includes/constants/class-express-checkout-hong-kong-states.php @@ -0,0 +1,360 @@ +express_checkout_button_helper->normalize_state(); + // In case the state is required, but is missing, add a more descriptive error notice. $this->express_checkout_button_helper->validate_state(); - $this->express_checkout_button_helper->normalize_state(); - WC()->checkout()->process_checkout(); } catch ( Exception $e ) { Logger::error( 'Failed to process express checkout payment: ' . $e ); 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 285ce659d94..18f5de0525e 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 @@ -946,6 +946,46 @@ public function normalize_state() { $billing_state = ! empty( $_POST['billing_state'] ) ? wc_clean( wp_unslash( $_POST['billing_state'] ) ) : ''; $shipping_state = ! empty( $_POST['shipping_state'] ) ? wc_clean( wp_unslash( $_POST['shipping_state'] ) ) : ''; + // Due to a bug in Apple Pay, the "Region" part of a Hong Kong address is delivered in + // `shipping_postcode`, so we need some special case handling for that. According to + // our sources at Apple Pay people will sometimes use the district or even sub-district + // for this value. As such we check against all regions, districts, and sub-districts + // with both English and Mandarin spelling. + // + // @reykjalin: The check here is quite elaborate in an attempt to make sure this doesn't break once + // Apple Pay fixes the bug that causes address values to be in the wrong place. Because of that the + // algorithm becomes: + // 1. Use the supplied state if it's valid (in case Apple Pay bug is fixed) + // 2. Use the value supplied in the postcode if it's a valid HK region (equivalent to a WC state). + // 3. Fall back to the value supplied in the state. This will likely cause a validation error, in + // which case a merchant can reach out to us so we can either: 1) add whatever the customer used + // as a state to our list of valid states; or 2) let them know the customer must spell the state + // in some way that matches our list of valid states. + // + // @reykjalin: This HK specific sanitazation *should be removed* once Apple Pay fix + // the address bug. More info on that in pc4etw-bY-p2. + if ( 'HK' === $billing_country ) { + include_once WCPAY_ABSPATH . 'includes/constants/class-express-checkout-hong-kong-states.php'; + + if ( ! \WCPay\Constants\Express_Checkout_Hong_Kong_States::is_valid_state( strtolower( $billing_state ) ) ) { + $billing_postcode = ! empty( $_POST['billing_postcode'] ) ? wc_clean( wp_unslash( $_POST['billing_postcode'] ) ) : ''; + if ( \WCPay\Constants\Express_Checkout_Hong_Kong_States::is_valid_state( strtolower( $billing_postcode ) ) ) { + $billing_state = $billing_postcode; + } + } + } + if ( 'HK' === $shipping_country ) { + include_once WCPAY_ABSPATH . 'includes/constants/class-express-checkout-hong-kong-states.php'; + + if ( ! \WCPay\Constants\Express_Checkout_Hong_Kong_States::is_valid_state( strtolower( $shipping_state ) ) ) { + $shipping_postcode = ! empty( $_POST['shipping_postcode'] ) ? wc_clean( wp_unslash( $_POST['shipping_postcode'] ) ) : ''; + if ( \WCPay\Constants\Express_Checkout_Hong_Kong_States::is_valid_state( strtolower( $shipping_postcode ) ) ) { + $shipping_state = $shipping_postcode; + } + } + } + + // Finally we normalize the state value we want to process. if ( $billing_state && $billing_country ) { $_POST['billing_state'] = $this->get_normalized_state( $billing_state, $billing_country ); } From 737aca04b36720ca54acb7377c65e954dba5c0de Mon Sep 17 00:00:00 2001 From: bruce aldridge Date: Mon, 9 Dec 2024 11:20:55 +1300 Subject: [PATCH 23/83] Fix error log messages incorrecty categorized as info (#9890) Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com> --- changelog/fix-9889-log-level | 4 ++++ includes/class-logger.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-9889-log-level diff --git a/changelog/fix-9889-log-level b/changelog/fix-9889-log-level new file mode 100644 index 00000000000..d2f54e24c1a --- /dev/null +++ b/changelog/fix-9889-log-level @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Errors were incorrectly marked as info in logs. diff --git a/includes/class-logger.php b/includes/class-logger.php index 3384d0fe443..1ce8ae255ab 100644 --- a/includes/class-logger.php +++ b/includes/class-logger.php @@ -36,7 +36,7 @@ class Logger { * 'debug': Debug-level messages. */ public static function log( $message, $level = 'info' ) { - wcpay_get_container()->get( InternalLogger::class )->log( $message ); + wcpay_get_container()->get( InternalLogger::class )->log( $message, $level ); } /** From 15e7ef8c89f87abf07cd6c748a39a98a822ef934 Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Mon, 9 Dec 2024 16:17:48 +0100 Subject: [PATCH 24/83] Add check for `wc_get_payment_gateway_by_order` (#9903) Co-authored-by: Marcin Bot --- changelog/fix-add-payment-method-check | 5 +++++ includes/class-woopay-tracker.php | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-add-payment-method-check diff --git a/changelog/fix-add-payment-method-check b/changelog/fix-add-payment-method-check new file mode 100644 index 00000000000..4ffc9e6342f --- /dev/null +++ b/changelog/fix-add-payment-method-check @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Added a check for the gateway id before comparing it + + diff --git a/includes/class-woopay-tracker.php b/includes/class-woopay-tracker.php index 538ec873dc8..fbcdd6bc948 100644 --- a/includes/class-woopay-tracker.php +++ b/includes/class-woopay-tracker.php @@ -543,7 +543,7 @@ public function checkout_order_processed( $order_id ) { $properties = [ 'payment_title' => 'other' ]; // If the order was placed using WooCommerce Payments, record the payment title using Tracks. - if ( strpos( $payment_gateway->id, 'woocommerce_payments' ) === 0 ) { + if ( isset( $payment_gateway->id ) && strpos( $payment_gateway->id, 'woocommerce_payments' ) === 0 ) { $order = wc_get_order( $order_id ); $payment_title = $order->get_payment_method_title(); $properties = [ 'payment_title' => $payment_title ]; From 0ea8272f82e760b0648c072d0307b1bc76582f90 Mon Sep 17 00:00:00 2001 From: Francesco Date: Tue, 10 Dec 2024 14:54:01 +0100 Subject: [PATCH 25/83] fix: console warning for useEffect on plugins page (#9913) --- changelog/fix-use-effect-console-warning | 5 +++++ client/plugins-page/index.js | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-use-effect-console-warning diff --git a/changelog/fix-use-effect-console-warning b/changelog/fix-use-effect-console-warning new file mode 100644 index 00000000000..45219e7b39a --- /dev/null +++ b/changelog/fix-use-effect-console-warning @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: fix: console warning on plugins page + + diff --git a/client/plugins-page/index.js b/client/plugins-page/index.js index 24d59e65fa5..b960794f65d 100644 --- a/client/plugins-page/index.js +++ b/client/plugins-page/index.js @@ -77,12 +77,12 @@ const PluginsPage = () => { useEffect( () => { // If the survey is dismissed skip event listeners. if ( isModalDismissed() ) { - return null; + return; } // Abort if the deactivation link is not present. if ( deactivationLink === null ) { - return null; + return; } // Handle click event. From adbac29236168227b9e191a6c7ac4a15a2018d32 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Tue, 10 Dec 2024 18:41:04 +0100 Subject: [PATCH 26/83] Update confirmation modal after progressive onboarding (#9915) --- changelog/update-confirmation-modal-nox | 4 + .../index.tsx | 98 +++++++++---------- .../style.scss | 85 +++++++++++----- .../test/index.test.tsx | 4 +- client/overview/task-list/strings.tsx | 5 +- client/overview/test/index.js | 2 +- 6 files changed, 116 insertions(+), 82 deletions(-) create mode 100644 changelog/update-confirmation-modal-nox diff --git a/changelog/update-confirmation-modal-nox b/changelog/update-confirmation-modal-nox new file mode 100644 index 00000000000..0ffd1af6127 --- /dev/null +++ b/changelog/update-confirmation-modal-nox @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update confirmation modal after onbarding diff --git a/client/overview/modal/progressive-onboarding-eligibility/index.tsx b/client/overview/modal/progressive-onboarding-eligibility/index.tsx index d4b3f79021f..6f6be89a707 100644 --- a/client/overview/modal/progressive-onboarding-eligibility/index.tsx +++ b/client/overview/modal/progressive-onboarding-eligibility/index.tsx @@ -5,8 +5,9 @@ import React, { useEffect, useState } from 'react'; import { __, sprintf } from '@wordpress/i18n'; import { addQueryArgs } from '@wordpress/url'; import { Button, Modal } from '@wordpress/components'; -import { Icon, store, widget, tool } from '@wordpress/icons'; +import { Icon, store, currencyDollar } from '@wordpress/icons'; import { useDispatch } from '@wordpress/data'; +import interpolateComponents from '@automattic/interpolate-components'; /** * Internal dependencies @@ -59,75 +60,74 @@ const ProgressiveOnboardingEligibilityModal: React.FC = () => { setModalVisible( false ); }; - // Workaround to remove Modal header from the modal until `hideHeader` prop can be used. - useEffect( () => { - document - .querySelector( - '.wcpay-progressive-onboarding-eligibility-modal .components-modal__header-heading-container' - ) - ?.remove(); - }, [] ); - if ( ! modalVisible || modalDismissed ) return null; return ( -

- { __( 'You’re ready to sell.', 'woocommerce-payments' ) } -

- { __( - 'Start selling now and fast track the setup process, or continue the process to set up payouts with WooPayments.', - 'woocommerce-payments' - ) } + { interpolateComponents( { + mixedString: sprintf( + __( + 'Great news — your %s account has been activated. You can now start accepting payments on your store, subject to {{restrictionsLink}}certain restrictions{{/restrictionsLink}}.', + 'woocommerce-payments' + ), + 'WooPayments' + ), + components: { + restrictionsLink: ( + // eslint-disable-next-line jsx-a11y/anchor-has-content + + ), + }, + } ) }

- -

+ +
+

+ { __( + 'Start selling instantly', + 'woocommerce-payments' + ) } +

{ __( - 'Start selling instantly', + 'You have 30 days from your first transaction or until you reach $5,000 in sales to verify your information and set up payouts.', 'woocommerce-payments' ) } -

- { sprintf( - /* translators: %s: WooPayments */ - __( - '%s enables you to start processing credit card payments right away.', - 'woocommerce-payments' - ), - 'WooPayments' - ) } -
-
- -

- { __( 'Quick and easy setup', 'woocommerce-payments' ) } -

- { __( - 'The setup process is super simple and ensures your store is ready to accept card payments.', - 'woocommerce-payments' - ) } +
- -

- { __( 'Flexible process', 'woocommerce-payments' ) } -

- { __( - 'You have a $5,000 balance limit or 30 days from your first transaction to verify and set up payouts in your account.', - 'woocommerce-payments' - ) } + +
+

+ { __( + 'Start receiving payouts', + 'woocommerce-payments' + ) } +

+ { __( + 'Provide some additional details about your business so you can continue accepting payments and begin receiving payouts without restrictions.', + 'woocommerce-payments' + ) } +
diff --git a/client/components/sandbox-mode-switch-to-live-notice/modal/style.scss b/client/components/sandbox-mode-switch-to-live-notice/modal/style.scss index b4067be8ba1..ff2b10db5f0 100644 --- a/client/components/sandbox-mode-switch-to-live-notice/modal/style.scss +++ b/client/components/sandbox-mode-switch-to-live-notice/modal/style.scss @@ -1,21 +1,30 @@ .wcpay-setup-real-payments-modal { - color: $gray-900; - fill: $studio-woocommerce-purple-50; + &.components-modal__frame { + width: 512px; + + @media screen and ( max-width: $break-small ) { + height: fit-content; + margin: auto auto; + max-width: 90vw; + } + } .components-modal__content { box-sizing: border-box; max-width: 600px; - margin: auto; + margin: 0; padding: $gap-smaller $gap-larger $gap-larger; } .components-modal__header { position: initial; - padding: 0; + padding: 24px 0 16px 0; + height: auto; border: 0; h1 { @include wp-title-small; + font-weight: 300; margin-bottom: $gap-smaller; } } @@ -24,20 +33,36 @@ @include wp-title-small; } - &__headline { - font-weight: 600; - } - &__content { - display: grid; - grid-template-columns: auto 1fr; - gap: $gap; - padding: $gap-smallest; - align-items: center; - margin-bottom: $gap-large; + display: flex; + gap: $gap-large; + flex-direction: column; + padding: $gap-small 0 $gap 0; + + &__item { + p { + line-height: 20px; + margin: 0; + } + } + + &__item-flex { + display: flex; + gap: $gap; + padding-right: $gap-large; + + &__description { + color: $gray-700; + } + p { + line-height: 20px; + margin: 0; + } + } } &__footer { @include modal-footer-buttons; + padding-top: $gap-large; } } diff --git a/client/components/sandbox-mode-switch-to-live-notice/modal/test/index.test.tsx b/client/components/sandbox-mode-switch-to-live-notice/modal/test/index.test.tsx index 2f82a5a263b..e2341aaa3a5 100644 --- a/client/components/sandbox-mode-switch-to-live-notice/modal/test/index.test.tsx +++ b/client/components/sandbox-mode-switch-to-live-notice/modal/test/index.test.tsx @@ -36,7 +36,7 @@ describe( 'Setup Live Payments Modal', () => { expect( screen.queryByText( - 'Before proceeding, please take note of the following information:' + "Before continuing, please make sure that you're aware of the following:" ) ).toBeInTheDocument(); } ); @@ -58,7 +58,7 @@ describe( 'Setup Live Payments Modal', () => { user.click( screen.getByRole( 'button', { - name: 'Continue setup', + name: 'Activate payments', } ) ); diff --git a/client/components/sandbox-mode-switch-to-live-notice/style.scss b/client/components/sandbox-mode-switch-to-live-notice/style.scss new file mode 100644 index 00000000000..0171d296b0a --- /dev/null +++ b/client/components/sandbox-mode-switch-to-live-notice/style.scss @@ -0,0 +1,5 @@ +.sandbox-mode-notice { + .wcpay-banner-notice__content { + display: flex; + } +} From 179f84ebbdddec992f10b2aea9164e78de4f83dd Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 12 Dec 2024 13:34:56 +0100 Subject: [PATCH 31/83] chore: remove ECE error assignment on loaderror (#9935) --- changelog/chore-remove-ece-error-assignment-on-loaderror | 5 +++++ client/express-checkout/index.js | 4 ---- client/tokenized-express-checkout/index.js | 4 ---- 3 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 changelog/chore-remove-ece-error-assignment-on-loaderror diff --git a/changelog/chore-remove-ece-error-assignment-on-loaderror b/changelog/chore-remove-ece-error-assignment-on-loaderror new file mode 100644 index 00000000000..cce991d09ba --- /dev/null +++ b/changelog/chore-remove-ece-error-assignment-on-loaderror @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: chore: remove ECE error assignment on loaderror + + diff --git a/client/express-checkout/index.js b/client/express-checkout/index.js index 6f36a3e6b59..0e2239946b6 100644 --- a/client/express-checkout/index.js +++ b/client/express-checkout/index.js @@ -256,10 +256,6 @@ jQuery( ( $ ) => { expressCheckoutButtonUi.renderButton( eceButton ); eceButton.on( 'loaderror', () => { - wcPayECEError = __( - 'The cart is incompatible with express checkout.', - 'woocommerce-payments' - ); if ( ! document.getElementById( 'wcpay-woopay-button' ) ) { expressCheckoutButtonUi.getButtonSeparator().hide(); } diff --git a/client/tokenized-express-checkout/index.js b/client/tokenized-express-checkout/index.js index 940aa1462b8..d18cb9f5be5 100644 --- a/client/tokenized-express-checkout/index.js +++ b/client/tokenized-express-checkout/index.js @@ -235,10 +235,6 @@ jQuery( ( $ ) => { expressCheckoutButtonUi.renderButton( eceButton ); eceButton.on( 'loaderror', () => { - wcPayECEError = __( - 'The cart is incompatible with express checkout.', - 'woocommerce-payments' - ); if ( ! document.getElementById( 'wcpay-woopay-button' ) ) { expressCheckoutButtonUi.getButtonSeparator().hide(); } From 9d22a75478075c9bc438275b8aeb305b51260073 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Thu, 12 Dec 2024 13:49:58 +0100 Subject: [PATCH 32/83] Move from docker-based to standalone Jurassic Tube setup (#9839) Co-authored-by: Timur Karimov --- .gitignore | 3 +++ bin/jurassic-tube-setup.sh | 43 ++++++++++++++++++++++--------- changelog/update-to-standalone-jt | 4 +++ package.json | 4 +-- 4 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 changelog/update-to-standalone-jt diff --git a/.gitignore b/.gitignore index d68b7c107c3..8a1b9da0119 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,6 @@ tests/e2e-pw/playwright/.cache/ tests/e2e-pw/tests/e2e-pw/.auth/* # Slate docs docs/rest-api/build/* + +# Jurassic Tube files +bin/jurassictube/ diff --git a/bin/jurassic-tube-setup.sh b/bin/jurassic-tube-setup.sh index 2859938e43e..aa7b2ec6fd3 100755 --- a/bin/jurassic-tube-setup.sh +++ b/bin/jurassic-tube-setup.sh @@ -3,29 +3,32 @@ # Exit if any command fails. set -e -echo "Checking if ${PWD}/docker/bin/jt directory exists..." +# Define Jurassic Tube directory using bin directory +JT_DIR="${PWD}/bin/jurassictube" -if [ -d "${PWD}/docker/bin/jt" ]; then - echo "${PWD}/docker/bin/jt already exists." +echo "Checking if ${JT_DIR} directory exists..." + +if [ -d "${JT_DIR}" ]; then + echo "${JT_DIR} already exists." else - echo "Creating ${PWD}/docker/bin/jt directory..." - mkdir -p "${PWD}/docker/bin/jt" + echo "Creating ${JT_DIR} directory..." + mkdir -p "${JT_DIR}" fi -echo "Downloading the latest version of the installer script..." +echo "Checking if the installer is present and downloading it if not..." echo # Download the installer (if it's not already present): -if [ ! -f "${PWD}/docker/bin/jt/installer.sh" ]; then - # Download the installer script: - curl "https://jurassic.tube/get-installer.php?env=wcpay" -o ${PWD}/docker/bin/jt/installer.sh && chmod +x ${PWD}/docker/bin/jt/installer.sh +if [ ! -f "${JT_DIR}/installer.sh" ]; then + echo "Downloading the standalone installer..." + curl "https://jurassic.tube/installer-standalone.sh" -o "${JT_DIR}/installer.sh" && chmod +x "${JT_DIR}/installer.sh" fi echo "Running the installation script..." echo # Run the installer script -source $PWD/docker/bin/jt/installer.sh +"${JT_DIR}/installer.sh" echo read -p "Go to https://jurassic.tube/ in a browser, paste your public key which was printed above into the box, and click 'Add Public Key'. Press enter to continue" @@ -40,8 +43,24 @@ echo read -p "Please enter your Automattic/WordPress.com username: " username echo -${PWD}/docker/bin/jt/config.sh username ${username} -${PWD}/docker/bin/jt/config.sh subdomain ${subdomain} +if [ ! -f "${JT_DIR}/config.env" ]; then + touch "${JT_DIR}/config.env" +else + > "${JT_DIR}/config.env" +fi + +# Find the WordPress container section and get its port +PORT=$(docker ps | grep woocommerce_payments_wordpress | sed -En "s/.*0:([0-9]+).*/\1/p") + +# Use default if extraction failed +if [ -z "$PORT" ]; then + PORT=8082 # Default fallback + echo "Could not extract WordPress container port, using default: ${PORT}" +fi + +echo "username=${username}" >> "${JT_DIR}/config.env" +echo "subdomain=${subdomain}" >> "${JT_DIR}/config.env" +echo "localhost=localhost:${PORT}" >> "${JT_DIR}/config.env" echo "Setup complete!" echo "Use the command: npm run tube:start from the root directory of your WC Payments project to start running Jurassic Tube." diff --git a/changelog/update-to-standalone-jt b/changelog/update-to-standalone-jt new file mode 100644 index 00000000000..4df87f235ec --- /dev/null +++ b/changelog/update-to-standalone-jt @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Update the tunelling setup. diff --git a/package.json b/package.json index 4fa803a245c..d69725ac2da 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "format:css": "npm run format:provided '**/*.scss' '**/*.css'", "format:provided": "prettier --write", "tube:setup": "./bin/jurassic-tube-setup.sh", - "tube:start": "./docker/bin/jt/tunnel.sh", - "tube:stop": "./docker/bin/jt/tunnel.sh break", + "tube:start": "source ./bin/jurassictube/config.env && jurassictube -u \"$username\" -s \"$subdomain\" -h \"$localhost\"", + "tube:stop": "source ./bin/jurassictube/config.env && jurassictube -b -s \"$subdomain\"", "psalm": "./bin/run-psalm.sh", "xdebug:toggle": "docker compose exec -u root wordpress /var/www/html/wp-content/plugins/woocommerce-payments/bin/xdebug-toggle.sh", "changelog": "./vendor/bin/changelogger add", From f08a72744661194b5cef15fb298d07609237f5d1 Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 12 Dec 2024 15:38:45 +0100 Subject: [PATCH 33/83] feat: tokenized ECE product page compatibility (#9877) --- ...nized-ece-product-page-base-implementation | 5 + .../compatibility/wc-deposits.js | 32 +- .../compatibility/wc-order-attribution.js | 4 +- ...oduct-variations.js => wc-product-page.js} | 46 +- .../debounce.js | 0 .../event-handlers.js | 2 +- client/tokenized-express-checkout/index.js | 449 +++++++----------- 7 files changed, 231 insertions(+), 307 deletions(-) create mode 100644 changelog/feat-tokenized-ece-product-page-base-implementation rename client/tokenized-express-checkout/compatibility/{wc-product-variations.js => wc-product-page.js} (62%) rename client/{tokenized-payment-request => tokenized-express-checkout}/debounce.js (100%) diff --git a/changelog/feat-tokenized-ece-product-page-base-implementation b/changelog/feat-tokenized-ece-product-page-base-implementation new file mode 100644 index 00000000000..e0f342c1623 --- /dev/null +++ b/changelog/feat-tokenized-ece-product-page-base-implementation @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: feat: tokenized ECE product page base implementation + + diff --git a/client/tokenized-express-checkout/compatibility/wc-deposits.js b/client/tokenized-express-checkout/compatibility/wc-deposits.js index 352b498b4b2..7993d08db78 100644 --- a/client/tokenized-express-checkout/compatibility/wc-deposits.js +++ b/client/tokenized-express-checkout/compatibility/wc-deposits.js @@ -1,15 +1,33 @@ /* global jQuery */ +/** + * External dependencies + */ +import { addFilter, doAction } from '@wordpress/hooks'; + jQuery( ( $ ) => { - // WooCommerce Deposits support. - // Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed. $( 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' ).on( 'change', () => { - $( 'form' ) - .has( - 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' - ) - .trigger( 'woocommerce_variation_has_changed' ); + doAction( 'wcpay.express-checkout.update-button-data' ); } ); } ); +addFilter( + 'wcpay.express-checkout.cart-add-item', + 'automattic/wcpay/express-checkout', + ( productData ) => { + const depositsData = {}; + if ( jQuery( 'input[name=wc_deposit_option]' ).length ) { + depositsData.wc_deposit_option = jQuery( + 'input[name=wc_deposit_option]:checked' + ).val(); + } + if ( jQuery( 'input[name=wc_deposit_payment_plan]' ).length ) { + depositsData.wc_deposit_payment_plan = jQuery( + 'input[name=wc_deposit_payment_plan]:checked' + ).val(); + } + + return { ...productData, ...depositsData }; + } +); diff --git a/client/tokenized-express-checkout/compatibility/wc-order-attribution.js b/client/tokenized-express-checkout/compatibility/wc-order-attribution.js index a707f8330ab..96133d25559 100644 --- a/client/tokenized-express-checkout/compatibility/wc-order-attribution.js +++ b/client/tokenized-express-checkout/compatibility/wc-order-attribution.js @@ -6,8 +6,8 @@ import { addFilter } from '@wordpress/hooks'; addFilter( - 'wcpay.payment-request.cart-place-order-extension-data', - 'automattic/wcpay/payment-request', + 'wcpay.express-checkout.cart-place-order-extension-data', + 'automattic/wcpay/express-checkout', ( extensionData ) => { const orderAttributionValues = jQuery( '#wcpay-express-checkout__order-attribution-inputs input' diff --git a/client/tokenized-express-checkout/compatibility/wc-product-variations.js b/client/tokenized-express-checkout/compatibility/wc-product-page.js similarity index 62% rename from client/tokenized-express-checkout/compatibility/wc-product-variations.js rename to client/tokenized-express-checkout/compatibility/wc-product-page.js index 775a894fec4..3c242f046cf 100644 --- a/client/tokenized-express-checkout/compatibility/wc-product-variations.js +++ b/client/tokenized-express-checkout/compatibility/wc-product-page.js @@ -1,31 +1,41 @@ /* global jQuery */ +/** + * Internal dependencies + */ +import expressCheckoutButtonUi from '../button-ui'; +import debounce from '../debounce'; /** * External dependencies */ -import { addFilter, applyFilters } from '@wordpress/hooks'; -import paymentRequestButtonUi from '../button-ui'; +import { addFilter, doAction } from '@wordpress/hooks'; jQuery( ( $ ) => { $( document.body ).on( 'woocommerce_variation_has_changed', async () => { - try { - paymentRequestButtonUi.blockButton(); - - await applyFilters( - 'wcpay.payment-request.update-button-data', - Promise.resolve() - ); - - paymentRequestButtonUi.unblockButton(); - } catch ( e ) { - paymentRequestButtonUi.hide(); - } + doAction( 'wcpay.express-checkout.update-button-data' ); } ); } ); +// Block the payment request button as soon as an "input" event is fired, to avoid sync issues +// when the customer clicks on the button before the debounced event is processed. +jQuery( ( $ ) => { + const $quantityInput = $( '.quantity' ); + const handleQuantityChange = () => { + expressCheckoutButtonUi.blockButton(); + }; + $quantityInput.on( 'input', '.qty', handleQuantityChange ); + $quantityInput.on( + 'input', + '.qty', + debounce( 250, async () => { + doAction( 'wcpay.express-checkout.update-button-data' ); + } ) + ); +} ); + addFilter( - 'wcpay.payment-request.cart-add-item', - 'automattic/wcpay/payment-request', + 'wcpay.express-checkout.cart-add-item', + 'automattic/wcpay/express-checkout', ( productData ) => { const $variationInformation = jQuery( '.single_variation_wrap' ); if ( ! $variationInformation.length ) { @@ -42,8 +52,8 @@ addFilter( } ); addFilter( - 'wcpay.payment-request.cart-add-item', - 'automattic/wcpay/payment-request', + 'wcpay.express-checkout.cart-add-item', + 'automattic/wcpay/express-checkout', ( productData ) => { const $variationsForm = jQuery( '.variations_form' ); if ( ! $variationsForm.length ) { diff --git a/client/tokenized-payment-request/debounce.js b/client/tokenized-express-checkout/debounce.js similarity index 100% rename from client/tokenized-payment-request/debounce.js rename to client/tokenized-express-checkout/debounce.js diff --git a/client/tokenized-express-checkout/event-handlers.js b/client/tokenized-express-checkout/event-handlers.js index c2d3ad557ef..cc300e36ef5 100644 --- a/client/tokenized-express-checkout/event-handlers.js +++ b/client/tokenized-express-checkout/event-handlers.js @@ -118,7 +118,7 @@ export const onConfirmHandler = async ( paymentMethod.id ), extensions: applyFilters( - 'wcpay.payment-request.cart-place-order-extension-data', + 'wcpay.express-checkout.cart-place-order-extension-data', {} ), } ); diff --git a/client/tokenized-express-checkout/index.js b/client/tokenized-express-checkout/index.js index d18cb9f5be5..eca8a2f8a36 100644 --- a/client/tokenized-express-checkout/index.js +++ b/client/tokenized-express-checkout/index.js @@ -1,6 +1,9 @@ /* global jQuery, wcpayExpressCheckoutParams */ +/** + * External dependencies + */ import { __ } from '@wordpress/i18n'; -import { debounce } from 'lodash'; +import { addAction, removeAction } from '@wordpress/hooks'; /** * Internal dependencies @@ -9,7 +12,7 @@ import WCPayAPI from '../checkout/api'; import '../checkout/express-checkout-buttons.scss'; import './compatibility/wc-deposits'; import './compatibility/wc-order-attribution'; -import './compatibility/wc-product-variations'; +import './compatibility/wc-product-page'; import { getExpressCheckoutButtonAppearance, getExpressCheckoutButtonStyleSettings, @@ -29,13 +32,66 @@ import { getCartApiHandler, } from './event-handlers'; import ExpressCheckoutOrderApi from './order-api'; +import ExpressCheckoutCartApi from './cart-api'; import { getUPEConfig } from 'wcpay/utils/checkout'; import expressCheckoutButtonUi from './button-ui'; import { transformCartDataForDisplayItems, transformCartDataForShippingRates, transformPrice, -} from 'wcpay/tokenized-express-checkout/transformers/wc-to-stripe'; +} from './transformers/wc-to-stripe'; + +let cachedCartData = null; +const noop = () => null; +const fetchNewCartData = async () => { + if ( getExpressCheckoutData( 'button_context' ) !== 'product' ) { + return await getCartApiHandler().getCart(); + } + + // creating a new cart and clearing it afterward, + // to avoid scenarios where the stock for a product with limited (or low) availability is added to the cart, + // preventing other customers from purchasing. + const temporaryCart = new ExpressCheckoutCartApi(); + temporaryCart.useSeparateCart(); + + const cartData = await temporaryCart.addProductToCart(); + + // no need to wait for the request to end, it can be done asynchronously. + // using `.finally( noop )` to avoid annoying IDE warnings. + temporaryCart.emptyCart().finally( noop ); + + return cartData; +}; + +const getServerSideExpressCheckoutProductData = () => { + const requestShipping = + getExpressCheckoutData( 'product' )?.needs_shipping ?? false; + const displayItems = ( + getExpressCheckoutData( 'product' )?.displayItems ?? [] + ).map( ( { label, amount } ) => ( { + name: label, + amount, + } ) ); + const shippingRates = requestShipping + ? [ + { + id: 'pending', + displayName: __( 'Pending', 'woocommerce-payments' ), + amount: 0, + }, + ] + : undefined; + + return { + total: getExpressCheckoutData( 'product' )?.total.amount, + currency: getExpressCheckoutData( 'product' )?.currency, + requestShipping, + shippingRates, + requestPhone: + getExpressCheckoutData( 'checkout' )?.needs_payer_phone ?? false, + displayItems, + }; +}; jQuery( ( $ ) => { // Don't load if blocks checkout is being loaded. @@ -47,7 +103,6 @@ jQuery( ( $ ) => { } const publishableKey = getExpressCheckoutData( 'stripe' ).publishableKey; - const quantityInputSelector = '.quantity .qty[type=number]'; if ( ! publishableKey ) { // If no configuration is present, probably this is not the checkout page. @@ -83,43 +138,10 @@ jQuery( ( $ ) => { $separator: jQuery( '#wcpay-express-checkout-button-separator' ), } ); - let wcPayECEError = ''; - const defaultErrorMessage = __( - 'There was an error getting the product information.', - 'woocommerce-payments' - ); - /** * Object to handle Stripe payment forms. */ const wcpayECE = { - getAttributes: function () { - const select = $( '.variations_form' ).find( '.variations select' ); - const data = {}; - let count = 0; - let chosen = 0; - - select.each( function () { - const attributeName = - $( this ).data( 'attribute_name' ) || - $( this ).attr( 'name' ); - const value = $( this ).val() || ''; - - if ( value.length > 0 ) { - chosen++; - } - - count++; - data[ attributeName ] = value; - } ); - - return { - count: count, - chosenCount: chosen, - data: data, - }; - }, - /** * Abort the payment and display error messages. * @@ -160,57 +182,6 @@ jQuery( ( $ ) => { window.location = url; }, - /** - * Adds the item to the cart and return cart details. - * - * @return {Promise} Promise for the request to the server. - */ - addToCart: () => { - let productId = $( '.single_add_to_cart_button' ).val(); - - // Check if product is a variable product. - if ( $( '.single_variation_wrap' ).length ) { - productId = $( '.single_variation_wrap' ) - .find( 'input[name="product_id"]' ) - .val(); - } - - if ( $( '.wc-bookings-booking-form' ).length ) { - productId = $( '.wc-booking-product-id' ).val(); - } - - const data = { - product_id: productId, - qty: $( quantityInputSelector ).val(), - attributes: $( '.variations_form' ).length - ? wcpayECE.getAttributes().data - : [], - }; - - // Add extension data to the POST body - const formData = $( 'form.cart' ).serializeArray(); - $.each( formData, ( i, field ) => { - if ( /^(addon-|wc_)/.test( field.name ) ) { - if ( /\[\]$/.test( field.name ) ) { - const fieldName = field.name.substring( - 0, - field.name.length - 2 - ); - if ( data[ fieldName ] ) { - data[ fieldName ].push( field.value ); - } else { - data[ fieldName ] = [ field.value ]; - } - } else { - data[ field.name ] = field.value; - } - } - } ); - - // TODO ~FR: replace with cartApi - return api.expressCheckoutECEAddToCart( data ); - }, - /** * Starts the Express Checkout Element * @@ -219,7 +190,7 @@ jQuery( ( $ ) => { startExpressCheckoutElement: async ( options ) => { const stripe = await api.getStripe(); const elements = stripe.elements( { - mode: options.mode ?? 'payment', + mode: 'payment', amount: options.total, currency: options.currency, paymentMethodCreation: 'manual', @@ -276,17 +247,16 @@ jQuery( ( $ ) => { return; } - if ( wcPayECEError ) { - window.alert( wcPayECEError ); - return; - } - // Add products to the cart if everything is right. - // TODO ~FR: use cartApi - wcpayECE.addToCart(); + getCartApiHandler().addProductToCart(); } const clickOptions = { + // `options.displayItems`, `options.requestShipping`, `options.requestPhone`, `options.shippingRates`, + // are all coming from prior of the initialization. + // The "real" values will be updated once the button loads. + // They are preemptively initialized because the `event.resolve({})` + // needs to be called within 1 second of the `click` event. lineItems: options.displayItems, emailRequired: true, shippingAddressRequired: options.requestShipping, @@ -322,6 +292,15 @@ jQuery( ( $ ) => { eceButton.on( 'cancel', async () => { wcpayECE.paymentAborted = true; + + if ( + getExpressCheckoutData( 'button_context' ) === 'product' + ) { + // clearing the cart to avoid issues with products with low or limited availability + // being held hostage by customers cancelling the ECE. + getCartApiHandler().emptyCart(); + } + onCancelHandler(); } ); @@ -339,224 +318,136 @@ jQuery( ( $ ) => { } } ); - if ( getExpressCheckoutData( 'button_context' ) === 'product' ) { - wcpayECE.attachProductPageEventListeners( elements ); - } - }, - - getSelectedProductData: () => { - let productId = $( '.single_add_to_cart_button' ).val(); - - // Check if product is a variable product. - if ( $( '.single_variation_wrap' ).length ) { - productId = $( '.single_variation_wrap' ) - .find( 'input[name="product_id"]' ) - .val(); - } - - if ( $( '.wc-bookings-booking-form' ).length ) { - productId = $( '.wc-booking-product-id' ).val(); - } - - const addons = - $( '#product-addons-total' ).data( 'price_data' ) || []; - const addonValue = addons.reduce( - ( sum, addon ) => sum + addon.cost, - 0 + removeAction( + 'wcpay.express-checkout.update-button-data', + 'automattic/wcpay/express-checkout' ); + addAction( + 'wcpay.express-checkout.update-button-data', + 'automattic/wcpay/express-checkout', + async () => { + try { + expressCheckoutButtonUi.blockButton(); - // WC Deposits Support. - const depositObject = {}; - if ( $( 'input[name=wc_deposit_option]' ).length ) { - depositObject.wc_deposit_option = $( - 'input[name=wc_deposit_option]:checked' - ).val(); - } - if ( $( 'input[name=wc_deposit_payment_plan]' ).length ) { - depositObject.wc_deposit_payment_plan = $( - 'input[name=wc_deposit_payment_plan]:checked' - ).val(); - } + cachedCartData = await fetchNewCartData(); + // checking if items needed shipping, before assigning new cart data. + const didItemsNeedShipping = options.requestShipping; - const data = { - product_id: productId, - qty: $( quantityInputSelector ).val(), - attributes: $( '.variations_form' ).length - ? wcpayECE.getAttributes().data - : [], - addon_value: addonValue, - ...depositObject, - }; - - // TODO ~FR: replace with cartApi - return api.expressCheckoutECEGetSelectedProductData( data ); - }, - - attachProductPageEventListeners: ( elements ) => { - // WooCommerce Deposits support. - // Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed. - // Needs to be defined before the `woocommerce_variation_has_changed` event handler is set. - $( - 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' - ) - .off( 'change' ) - .on( 'change', () => { - $( 'form' ) - .has( - 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' - ) - .trigger( 'woocommerce_variation_has_changed' ); - } ); - - $( document.body ) - .off( 'woocommerce_variation_has_changed' ) - .on( 'woocommerce_variation_has_changed', () => { - expressCheckoutButtonUi.blockButton(); - - $.when( wcpayECE.getSelectedProductData() ) - .then( ( response ) => { - // TODO ~FR: this seems new - const isDeposits = wcpayECE.productHasDepositOption(); - /** - * If the customer aborted the express checkout, - * we need to re init the express checkout button to ensure the shipping - * options are refetched. If the customer didn't abort the express checkout, - * and the product's shipping status is consistent, - * we can simply update the express checkout button with the new total and display items. - */ - const needsShipping = - ! wcpayECE.paymentAborted && - getExpressCheckoutData( 'product' ) - .needs_shipping === response.needs_shipping; - - if ( ! isDeposits && needsShipping ) { - elements.update( { - amount: response.total.amount, - } ); - } else { - wcpayECE.reInitExpressCheckoutElement( - response - ); - } - } ) - .catch( () => { - expressCheckoutButtonUi.hideContainer(); - expressCheckoutButtonUi.getButtonSeparator().hide(); - } ) - .always( () => { - expressCheckoutButtonUi.unblockButton(); - } ); - } ); - - $( '.quantity' ) - .off( 'input', '.qty' ) - .on( - 'input', - '.qty', - debounce( () => { - expressCheckoutButtonUi.blockButton(); - wcPayECEError = ''; - - $.when( wcpayECE.getSelectedProductData() ) - .then( - ( response ) => { - // In case the server returns an unexpected response - if ( typeof response !== 'object' ) { - wcPayECEError = defaultErrorMessage; - } - - if ( - ! wcpayECE.paymentAborted && - getExpressCheckoutData( 'product' ) - .needs_shipping === - response.needs_shipping - ) { - elements.update( { - amount: response.total.amount, - } ); - } else { - wcpayECE.reInitExpressCheckoutElement( - response - ); - } + /** + * If the customer aborted the payment request, we need to re init the payment request button to ensure the shipping + * options are re-fetched. If the customer didn't abort the payment request, and the product's shipping status is + * consistent, we can simply update the payment request button with the new total and display items. + */ + if ( + ! wcpayECE.paymentAborted && + didItemsNeedShipping === + cachedCartData.needs_shipping + ) { + elements.update( { + total: { + label: getExpressCheckoutData( + 'total_label' + ), + amount: transformPrice( + parseInt( + cachedCartData.totals.total_price, + 10 + ) - + parseInt( + cachedCartData.totals + .total_refund || 0, + 10 + ), + cachedCartData.totals + ), }, - ( response ) => { - wcPayECEError = - response.responseJSON?.error ?? - defaultErrorMessage; - } - ) - .always( function () { - expressCheckoutButtonUi.unblockButton(); + displayItems: transformCartDataForDisplayItems( + cachedCartData + ), } ); - }, 250 ) - ); - }, + } else { + // the cachedCartData from the Store API will be used from now on, + // instead of the `product` attributes. + wcpayExpressCheckoutParams.product = null; - reInitExpressCheckoutElement: ( response ) => { - wcpayExpressCheckoutParams.product.needs_shipping = - response.needs_shipping; - wcpayExpressCheckoutParams.product.total = response.total; - wcpayExpressCheckoutParams.product.displayItems = - response.displayItems; - wcpayECE.init(); - }, + await wcpayECE.init(); + } - productHasDepositOption() { - return !! $( 'form' ).has( - 'input[name=wc_deposit_option],input[name=wc_deposit_payment_plan]' - ).length; + expressCheckoutButtonUi.unblockButton(); + } catch ( e ) { + expressCheckoutButtonUi.hide(); + } + } + ); }, /** * Initialize event handlers and UI state */ init: async () => { + // on product pages, we should be able to have `getExpressCheckoutData( 'product' )` from the backend, + // which saves us some AJAX calls. + if ( ! getExpressCheckoutData( 'product' ) && ! cachedCartData ) { + try { + cachedCartData = await fetchNewCartData(); + } catch ( e ) { + // if something fails here, we can likely fall back on `getExpressCheckoutData( 'product' )`. + } + } + + // once (and if) cart data has been fetched, we can safely clear product data from the backend. + if ( cachedCartData ) { + wcpayExpressCheckoutParams.product = undefined; + } + if ( getExpressCheckoutData( 'button_context' ) === 'product' ) { - await wcpayECE.startExpressCheckoutElement( { - mode: 'payment', - total: getExpressCheckoutData( 'product' )?.total.amount, - currency: getExpressCheckoutData( 'product' )?.currency, - requestShipping: - getExpressCheckoutData( 'product' )?.needs_shipping ?? - false, - requestPhone: - getExpressCheckoutData( 'checkout' ) - ?.needs_payer_phone ?? false, - displayItems: getExpressCheckoutData( 'product' ) - .displayItems, - } ); - } else { + // on product pages, we need to interact with an anonymous cart to check out the product, + // so that we don't affect the products in the main cart. + // On cart, checkout, place order pages we instead use the cart itself. + getCartApiHandler().useSeparateCart(); + } + + if ( cachedCartData ) { // If this is the cart page, or checkout page, or pay-for-order page, we need to request the cart details. - const cartData = await getCartApiHandler().getCart(); + // but if the data is not available, we can't render the button. const total = transformPrice( - parseInt( cartData.totals.total_price, 10 ) - - parseInt( cartData.totals.total_refund || 0, 10 ), - cartData.totals + parseInt( cachedCartData.totals.total_price, 10 ) - + parseInt( cachedCartData.totals.total_refund || 0, 10 ), + cachedCartData.totals ); if ( total === 0 ) { expressCheckoutButtonUi.hideContainer(); expressCheckoutButtonUi.getButtonSeparator().hide(); } else { await wcpayECE.startExpressCheckoutElement( { - mode: 'payment', total, - currency: cartData.totals.currency_code.toLowerCase(), + currency: cachedCartData.totals.currency_code.toLowerCase(), // pay-for-order should never display the shipping selection. requestShipping: getExpressCheckoutData( 'button_context' ) !== - 'pay_for_order' && cartData.needs_shipping, + 'pay_for_order' && + cachedCartData.needs_shipping, shippingRates: transformCartDataForShippingRates( - cartData + cachedCartData ), requestPhone: getExpressCheckoutData( 'checkout' ) ?.needs_payer_phone ?? false, displayItems: transformCartDataForDisplayItems( - cartData + cachedCartData ), } ); } + } else if ( + getExpressCheckoutData( 'button_context' ) === 'product' && + getExpressCheckoutData( 'product' ) + ) { + await wcpayECE.startExpressCheckoutElement( + getServerSideExpressCheckoutProductData() + ); + } else { + expressCheckoutButtonUi.hideContainer(); + expressCheckoutButtonUi.getButtonSeparator().hide(); } // After initializing a new express checkout button, we need to reset the paymentAborted flag. From 02da62a50aa12d5ab10da027f4ece009069e37f5 Mon Sep 17 00:00:00 2001 From: Guilherme Pressutto Date: Thu, 12 Dec 2024 11:47:33 -0300 Subject: [PATCH 34/83] Fixed UPE country detection in Checkout for non-logged in users (#9912) Co-authored-by: Timur Karimov --- changelog/fix-upe-country-selection | 4 ++++ client/checkout/utils/upe.js | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-upe-country-selection diff --git a/changelog/fix-upe-country-selection b/changelog/fix-upe-country-selection new file mode 100644 index 00000000000..478ffa1cfcd --- /dev/null +++ b/changelog/fix-upe-country-selection @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed UPE country detection in Checkout for non-logged in users diff --git a/client/checkout/utils/upe.js b/client/checkout/utils/upe.js index 500314b9f5b..af8e0427c04 100644 --- a/client/checkout/utils/upe.js +++ b/client/checkout/utils/upe.js @@ -375,10 +375,9 @@ export const togglePaymentMethodForCountry = ( upeElement ) => { billingInput = document.querySelector( '#billing_country' ); } - /* global wcpayCustomerData */ // in the case of "pay for order", there is no "billing country" input, so we need to rely on backend data. const billingCountry = - billingInput?.value || wcpayCustomerData?.billing_country || ''; + billingInput?.value || window?.wcpayCustomerData?.billing_country || ''; const upeContainer = upeElement?.closest( '.wc_payment_method' ); if ( supportedCountries.includes( billingCountry ) ) { From 52dbe03182d6a6fad5486ba2e6041a3043c5847c Mon Sep 17 00:00:00 2001 From: Malith Senaweera <6216000+malithsen@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:11:41 -0600 Subject: [PATCH 35/83] Pass footer header styles to WooPay (#9880) --- .../add-pass-footer-header-styles-to-woopay | 5 ++++ client/checkout/api/test/index.test.js | 3 +++ client/checkout/upe-styles/index.js | 12 ++++++++++ client/checkout/upe-styles/test/index.js | 23 +++++++++++++++++++ client/checkout/upe-styles/upe-styles.js | 13 +++++++++++ 5 files changed, 56 insertions(+) create mode 100644 changelog/add-pass-footer-header-styles-to-woopay diff --git a/changelog/add-pass-footer-header-styles-to-woopay b/changelog/add-pass-footer-header-styles-to-woopay new file mode 100644 index 00000000000..ab6375db250 --- /dev/null +++ b/changelog/add-pass-footer-header-styles-to-woopay @@ -0,0 +1,5 @@ +Significance: patch +Type: add +Comment: Impovements to WooPay themeing, which is not yet released to the public. + + diff --git a/client/checkout/api/test/index.test.js b/client/checkout/api/test/index.test.js index 8ec819ea4c0..7fdd80e9b3a 100644 --- a/client/checkout/api/test/index.test.js +++ b/client/checkout/api/test/index.test.js @@ -43,6 +43,9 @@ const mockAppearance = { '.Button': {}, '.Link': {}, '.Container': {}, + '.Footer': {}, + '.Footer-link': {}, + '.Header': {}, }, theme: 'stripe', variables: { diff --git a/client/checkout/upe-styles/index.js b/client/checkout/upe-styles/index.js index 5c775caf43e..45c9ea83578 100644 --- a/client/checkout/upe-styles/index.js +++ b/client/checkout/upe-styles/index.js @@ -156,6 +156,9 @@ export const appearanceSelectors = { buttonSelectors: [ '#place_order' ], linkSelectors: [ 'a' ], containerSelectors: [ '.woocommerce-checkout-review-order-table' ], + headerSelectors: [ '.site-header' ], + footerSelectors: [ '.site-footer' ], + footerLink: [ '.site-footer a' ], }, /** @@ -514,6 +517,12 @@ export const getAppearance = ( elementsLocation, forWooPay = false ) => { selectors.containerSelectors, '.Container' ); + const headerRules = getFieldStyles( selectors.headerSelectors, '.Header' ); + const footerRules = getFieldStyles( selectors.footerSelectors, '.Footer' ); + const footerLinkRules = getFieldStyles( + selectors.footerLink, + '.Footer--link' + ); const globalRules = { colorBackground: backgroundColor, colorText: paragraphRules.color, @@ -559,6 +568,9 @@ export const getAppearance = ( elementsLocation, forWooPay = false ) => { appearance.rules = { ...appearance.rules, '.Heading': headingRules, + '.Header': headerRules, + '.Footer': footerRules, + '.Footer-link': footerLinkRules, '.Button': buttonRules, '.Link': linkRules, '.Container': containerRules, diff --git a/client/checkout/upe-styles/test/index.js b/client/checkout/upe-styles/test/index.js index 96a602a4bbd..22fd181df90 100644 --- a/client/checkout/upe-styles/test/index.js +++ b/client/checkout/upe-styles/test/index.js @@ -225,6 +225,29 @@ describe( 'Getting styles for automated theming', () => { '.Container': { backgroundColor: 'rgba(0, 0, 0, 0)', }, + '.Footer': { + color: 'rgb(109, 109, 109)', + backgroundColor: 'rgba(0, 0, 0, 0)', + fontFamily: + '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', + fontSize: '12px', + padding: '10px', + }, + '.Footer-link': { + color: 'rgb(109, 109, 109)', + fontFamily: + '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', + fontSize: '12px', + padding: '10px', + }, + '.Header': { + color: 'rgb(109, 109, 109)', + backgroundColor: 'rgba(0, 0, 0, 0)', + fontFamily: + '"Source Sans Pro", HelveticaNeue-Light, "Helvetica Neue Light"', + fontSize: '12px', + padding: '10px', + }, }, labels: 'above', } ); diff --git a/client/checkout/upe-styles/upe-styles.js b/client/checkout/upe-styles/upe-styles.js index b578960317e..72903e459d7 100644 --- a/client/checkout/upe-styles/upe-styles.js +++ b/client/checkout/upe-styles/upe-styles.js @@ -78,6 +78,16 @@ const upeSupportedProperties = { ...borderOutlineBackgroundProps.slice( 1 ), // Remove backgroundColor ], '.Container': [ ...borderOutlineBackgroundProps ], + '.Header': [ + ...paddingColorProps, + ...borderOutlineBackgroundProps, + ...textFontTransitionProps, + ], + '.Footer': [ + ...paddingColorProps, + ...borderOutlineBackgroundProps, + ...textFontTransitionProps, + ], }; // Restricted properties allowed to generate the automated theming of UPE. @@ -113,6 +123,9 @@ export const upeRestrictedProperties = { '.TabLabel': upeSupportedProperties[ '.TabLabel' ], '.Block': upeSupportedProperties[ '.Block' ], '.Container': upeSupportedProperties[ '.Container' ], + '.Header': upeSupportedProperties[ '.Header' ], + '.Footer': upeSupportedProperties[ '.Footer' ], + '.Footer--link': upeSupportedProperties[ '.Text' ], '.Text': upeSupportedProperties[ '.Text' ], '.Text--redirect': upeSupportedProperties[ '.Text' ], }; From 9729695739be9e4b491ec91c454801c7f3dfda9e Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 12 Dec 2024 17:48:25 +0100 Subject: [PATCH 36/83] chore: remove tokeinzed payment request code (#9920) --- .eslintignore | 4 - ...emove-tokenized-payment-request-references | 5 + client/tokenized-payment-request/README.md | 4 - .../blocks/apple-pay-preview.js | 3 - .../tokenized-payment-request/blocks/index.js | 62 -- .../blocks/payment-request-express.js | 163 ---- .../blocks/use-initialization.js | 172 ---- client/tokenized-payment-request/button-ui.js | 47 - .../event-handlers.js | 178 ---- .../frontend-utils.js | 257 ------ client/tokenized-payment-request/index.js | 90 -- .../payment-request.js | 478 ---------- .../test/payment-request.test.js | 158 ---- client/tokenized-payment-request/tracking.js | 36 - .../transformers/stripe-to-wc.js | 103 --- ...ayments-payment-request-button-handler.php | 873 ------------------ psalm-baseline.xml | 7 - tests/js/jest.config.js | 2 - tests/unit/bootstrap.php | 2 - ...ayments-payment-request-button-handler.php | 650 ------------- 20 files changed, 5 insertions(+), 3289 deletions(-) create mode 100644 changelog/chore-remove-tokenized-payment-request-references delete mode 100644 client/tokenized-payment-request/README.md delete mode 100644 client/tokenized-payment-request/blocks/apple-pay-preview.js delete mode 100644 client/tokenized-payment-request/blocks/index.js delete mode 100644 client/tokenized-payment-request/blocks/payment-request-express.js delete mode 100644 client/tokenized-payment-request/blocks/use-initialization.js delete mode 100644 client/tokenized-payment-request/button-ui.js delete mode 100644 client/tokenized-payment-request/event-handlers.js delete mode 100644 client/tokenized-payment-request/frontend-utils.js delete mode 100644 client/tokenized-payment-request/index.js delete mode 100644 client/tokenized-payment-request/payment-request.js delete mode 100644 client/tokenized-payment-request/test/payment-request.test.js delete mode 100644 client/tokenized-payment-request/tracking.js delete mode 100644 client/tokenized-payment-request/transformers/stripe-to-wc.js delete mode 100644 includes/class-wc-payments-payment-request-button-handler.php delete mode 100644 tests/unit/test-class-wc-payments-payment-request-button-handler.php diff --git a/.eslintignore b/.eslintignore index e558812a35a..590187b9d5e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,7 +10,3 @@ vendor/* release/* tests/e2e/docker* tests/e2e/deps* - -# We'll delete the directory and its contents as part of https://github.com/Automattic/woocommerce-payments/issues/9722 . -# ignoring it because we're temporariily cleaning it up. -client/tokenized-payment-request diff --git a/changelog/chore-remove-tokenized-payment-request-references b/changelog/chore-remove-tokenized-payment-request-references new file mode 100644 index 00000000000..56dc3b0a0cc --- /dev/null +++ b/changelog/chore-remove-tokenized-payment-request-references @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: chore: remove tokeinzed payment request code + + diff --git a/client/tokenized-payment-request/README.md b/client/tokenized-payment-request/README.md deleted file mode 100644 index 2c92ba8d2ff..00000000000 --- a/client/tokenized-payment-request/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Tokenized Payment Request Button - -This directory contains the JS work done by the Heisenberg team to convert the PRBs to leverage the Store API. -We'll delete the directory and its contents as part of https://github.com/Automattic/woocommerce-payments/issues/9722 . diff --git a/client/tokenized-payment-request/blocks/apple-pay-preview.js b/client/tokenized-payment-request/blocks/apple-pay-preview.js deleted file mode 100644 index 6b6f543ed05..00000000000 --- a/client/tokenized-payment-request/blocks/apple-pay-preview.js +++ /dev/null @@ -1,3 +0,0 @@ -/* eslint-disable max-len */ -export const applePayImage = - "data:image/svg+xml,%3Csvg width='264' height='48' viewBox='0 0 264 48' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='264' height='48' rx='3' fill='black'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.114 16.6407C125.682 15.93 126.067 14.9756 125.966 14C125.135 14.0415 124.121 14.549 123.533 15.2602C123.006 15.8693 122.539 16.8641 122.661 17.7983C123.594 17.8797 124.526 17.3317 125.114 16.6407Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.955 17.982C124.601 17.9011 123.448 18.7518 122.801 18.7518C122.154 18.7518 121.163 18.0224 120.092 18.0421C118.696 18.0629 117.402 18.8524 116.694 20.1079C115.238 22.6196 116.31 26.3453 117.726 28.3909C118.414 29.4028 119.242 30.5174 120.334 30.4769C121.366 30.4365 121.77 29.8087 123.024 29.8087C124.277 29.8087 124.641 30.4769 125.733 30.4567C126.865 30.4365 127.573 29.4443 128.261 28.4313C129.049 27.2779 129.373 26.1639 129.393 26.1027C129.373 26.0825 127.209 25.2515 127.189 22.7606C127.169 20.6751 128.888 19.6834 128.969 19.6217C127.998 18.1847 126.481 18.0224 125.955 17.982Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M136.131 23.1804H138.834C140.886 23.1804 142.053 22.0752 142.053 20.1592C142.053 18.2432 140.886 17.1478 138.845 17.1478H136.131V23.1804ZM139.466 15.1582C142.411 15.1582 144.461 17.1903 144.461 20.1483C144.461 23.1172 142.369 25.1596 139.392 25.1596H136.131V30.3498H133.775V15.1582H139.466Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M152.198 26.224V25.3712L149.579 25.5397C148.106 25.6341 147.339 26.182 147.339 27.14C147.339 28.0664 148.138 28.6667 149.39 28.6667C150.988 28.6667 152.198 27.6449 152.198 26.224ZM145.046 27.2032C145.046 25.2551 146.529 24.1395 149.263 23.971L152.198 23.7922V22.9498C152.198 21.7181 151.388 21.0442 149.947 21.0442C148.758 21.0442 147.896 21.6548 147.717 22.5916H145.592C145.656 20.6232 147.507 19.1914 150.01 19.1914C152.703 19.1914 154.459 20.602 154.459 22.7917V30.351H152.282V28.5298H152.229C151.609 29.719 150.241 30.4666 148.758 30.4666C146.571 30.4666 145.046 29.1612 145.046 27.2032Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M156.461 34.4145V32.5934C156.608 32.6141 156.965 32.6354 157.155 32.6354C158.196 32.6354 158.785 32.1932 159.142 31.0564L159.353 30.3824L155.366 19.3281H157.827L160.604 28.298H160.657L163.434 19.3281H165.832L161.698 30.9402C160.752 33.6038 159.668 34.4778 157.376 34.4778C157.197 34.4778 156.618 34.4565 156.461 34.4145Z' fill='white'/%3E%3C/svg%3E%0A"; diff --git a/client/tokenized-payment-request/blocks/index.js b/client/tokenized-payment-request/blocks/index.js deleted file mode 100644 index f6cb3461102..00000000000 --- a/client/tokenized-payment-request/blocks/index.js +++ /dev/null @@ -1,62 +0,0 @@ -/* global wcpayConfig, wcpayPaymentRequestParams */ - -/** - * Internal dependencies - */ -import { PaymentRequestExpress } from './payment-request-express'; -import { applePayImage } from './apple-pay-preview'; -import { getConfig } from '../../utils/checkout'; -import { - getPaymentRequest, - transformCartDataForStoreAPI, -} from '../frontend-utils'; - -const PAYMENT_METHOD_NAME_PAYMENT_REQUEST = - 'woocommerce_payments_tokenized_cart_payment_request'; - -const ApplePayPreview = () => ; - -const tokenizedCartPaymentRequestPaymentMethod = ( api ) => ( { - name: PAYMENT_METHOD_NAME_PAYMENT_REQUEST, - content: ( - - ), - edit: , - canMakePayment: ( cartData ) => { - // If in the editor context, always return true to display the `edit` prop preview. - // https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/4101. - if ( getConfig( 'is_admin' ) ) { - return true; - } - - if ( typeof wcpayPaymentRequestParams === 'undefined' ) { - return false; - } - - if ( typeof wcpayConfig !== 'undefined' ) { - return false; - } - - return api.loadStripeForExpressCheckout().then( ( stripe ) => { - // Create a payment request and check if we can make a payment to determine whether to - // show the Payment Request Button or not. This is necessary because a browser might be - // able to load the Stripe JS object, but not support Payment Requests. - cartData = transformCartDataForStoreAPI( cartData, null ); - const pr = getPaymentRequest( { - stripe, - cartData, - } ); - - return pr.canMakePayment(); - } ); - }, - paymentMethodId: PAYMENT_METHOD_NAME_PAYMENT_REQUEST, - supports: { - features: getConfig( 'features' ), - }, -} ); - -export default tokenizedCartPaymentRequestPaymentMethod; diff --git a/client/tokenized-payment-request/blocks/payment-request-express.js b/client/tokenized-payment-request/blocks/payment-request-express.js deleted file mode 100644 index 3e852ae8486..00000000000 --- a/client/tokenized-payment-request/blocks/payment-request-express.js +++ /dev/null @@ -1,163 +0,0 @@ -/* global wcpayPaymentRequestParams */ - -/** - * External dependencies - */ -import { Elements, PaymentRequestButtonElement } from '@stripe/react-stripe-js'; -import { recordUserEvent } from 'tracks'; -import { useEffect, useState } from 'react'; - -/** - * Internal dependencies - */ -import { useInitialization } from './use-initialization'; -import { getPaymentRequestData } from '../frontend-utils'; - -/** - * PaymentRequestExpressComponent - * - * @param {Object} props Incoming props. - * - * @return {ReactNode} Payment Request button component. - */ -const PaymentRequestExpressComponent = ( { - api, - billing, - shippingData, - setExpressPaymentError, - onClick, - onClose, - onPaymentRequestAvailable, - cartData, -} ) => { - // TODO: Don't display custom button when result.requestType - // is `apple_pay` or `google_pay`. - const { - paymentRequest, - // paymentRequestType, - onButtonClick, - } = useInitialization( { - api, - billing, - shippingData, - setExpressPaymentError, - onClick, - onClose, - cartData, - } ); - - useEffect( () => { - if ( paymentRequest ) { - const orderAttribution = window?.wc_order_attribution; - if ( orderAttribution ) { - orderAttribution.setOrderTracking( - orderAttribution.params.allowTracking - ); - } - } - }, [ paymentRequest ] ); - - const { type, theme, height } = getPaymentRequestData( 'button' ); - - const paymentRequestButtonStyle = { - paymentRequestButton: { - type, - theme, - height: height + 'px', - }, - }; - - if ( ! paymentRequest ) { - return null; - } - - let paymentRequestType = ''; - - // Check the availability of the Payment Request API first. - paymentRequest.canMakePayment().then( ( result ) => { - if ( ! result ) { - return; - } - - // Set the payment request type. - if ( result.applePay ) { - paymentRequestType = 'apple_pay'; - } else if ( result.googlePay ) { - paymentRequestType = 'google_pay'; - } - onPaymentRequestAvailable( paymentRequestType ); - } ); - - const onPaymentRequestButtonClick = ( event ) => { - onButtonClick( event, paymentRequest ); - - const paymentRequestTypeEvents = { - google_pay: 'gpay_button_click', - apple_pay: 'applepay_button_click', - }; - - if ( paymentRequestTypeEvents.hasOwnProperty( paymentRequestType ) ) { - const paymentRequestEvent = - paymentRequestTypeEvents[ paymentRequestType ]; - recordUserEvent( paymentRequestEvent, { - source: wcpayPaymentRequestParams?.button_context, - } ); - } - }; - - return ( -
- - -
- ); -}; - -/** - * PaymentRequestExpress express payment method component. - * - * @param {Object} props PaymentMethodProps. - * - * @return {ReactNode} Stripe Elements component. - */ -export const PaymentRequestExpress = ( props ) => { - const { stripe } = props; - const [ paymentRequestType, setPaymentRequestType ] = useState( false ); - - const handlePaymentRequestAvailability = ( paymentType ) => { - setPaymentRequestType( paymentType ); - }; - - useEffect( () => { - if ( paymentRequestType ) { - const paymentRequestTypeEvents = { - google_pay: 'gpay_button_load', - apple_pay: 'applepay_button_load', - }; - - if ( - paymentRequestTypeEvents.hasOwnProperty( paymentRequestType ) - ) { - const event = paymentRequestTypeEvents[ paymentRequestType ]; - recordUserEvent( event, { - source: wcpayPaymentRequestParams?.button_context, - } ); - } - } - }, [ paymentRequestType ] ); - - return ( - - - - ); -}; diff --git a/client/tokenized-payment-request/blocks/use-initialization.js b/client/tokenized-payment-request/blocks/use-initialization.js deleted file mode 100644 index 360e2836eeb..00000000000 --- a/client/tokenized-payment-request/blocks/use-initialization.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * External dependencies - */ -import { useEffect, useState, useCallback } from '@wordpress/element'; -import { useStripe } from '@stripe/react-stripe-js'; - -/** - * Internal dependencies - */ -import { - shippingAddressChangeHandler, - shippingOptionChangeHandler, - paymentMethodHandler, -} from '../event-handlers.js'; - -import { - getPaymentRequest, - getPaymentRequestData, - transformCartDataForStoreAPI, - updatePaymentRequest, - displayLoginConfirmationDialog, -} from '../frontend-utils.js'; - -export const useInitialization = ( { - api, - billing, - shippingData, - setExpressPaymentError, - onClick, - onClose, - cartData, -} ) => { - cartData = transformCartDataForStoreAPI( null, { - ...cartData, - ...billing, - ...shippingData, - } ); - - const stripe = useStripe(); - - const [ paymentRequest, setPaymentRequest ] = useState( null ); - const [ isFinished, setIsFinished ] = useState( false ); - const [ paymentRequestType, setPaymentRequestType ] = useState( '' ); - - // Create the initial paymentRequest object. Note, we can't do anything if stripe isn't available yet or we have zero total. - useEffect( () => { - if ( - ! stripe || - ! billing?.cartTotal?.value || - isFinished || - paymentRequest - ) { - return; - } - - const pr = getPaymentRequest( { - stripe, - cartData, - } ); - - pr.canMakePayment().then( ( result ) => { - if ( result ) { - setPaymentRequest( pr ); - if ( result.applePay ) { - setPaymentRequestType( 'apple_pay' ); - } else if ( result.googlePay ) { - setPaymentRequestType( 'google_pay' ); - } else { - setPaymentRequestType( 'payment_request_api' ); - } - } - } ); - }, [ - stripe, - paymentRequest, - billing?.cartTotal?.value, - isFinished, - shippingData?.needsShipping, - billing?.cartTotalItems, - cartData, - ] ); - - // It's not possible to update the `requestShipping` property in the `paymentRequest` - // object, so when `needsShipping` changes, we need to reset the `paymentRequest` object. - useEffect( () => { - setPaymentRequest( null ); - }, [ shippingData.needsShipping ] ); - - // When the payment button is clicked, update the request and show it. - const onButtonClick = useCallback( - ( evt, pr ) => { - // If login is required, display redirect confirmation dialog. - if ( getPaymentRequestData( 'login_confirmation' ) ) { - evt.preventDefault(); - displayLoginConfirmationDialog( paymentRequestType ); - return; - } - - setIsFinished( false ); - setExpressPaymentError( '' ); - updatePaymentRequest( { - paymentRequest, - cartData, - } ); - onClick(); - - // We must manually call payment request `show()` for custom buttons. - if ( pr ) { - pr.show(); - } - }, - [ - setExpressPaymentError, - paymentRequest, - cartData, - onClick, - paymentRequestType, - ] - ); - - // Whenever paymentRequest changes, hook in event listeners. - useEffect( () => { - const cancelHandler = () => { - setIsFinished( false ); - setPaymentRequest( null ); - onClose(); - }; - - const completePayment = ( redirectUrl ) => { - setIsFinished( true ); - window.location = redirectUrl; - }; - - const abortPayment = ( paymentMethod, message ) => { - paymentMethod.complete( 'fail' ); - setIsFinished( true ); - setExpressPaymentError( message ); - }; - - paymentRequest?.on( 'shippingaddresschange', ( event ) => - shippingAddressChangeHandler( event ) - ); - - paymentRequest?.on( 'shippingoptionchange', ( event ) => - shippingOptionChangeHandler( event ) - ); - - paymentRequest?.on( 'paymentmethod', ( event ) => - paymentMethodHandler( api, completePayment, abortPayment, event ) - ); - - paymentRequest?.on( 'cancel', cancelHandler ); - - return () => { - paymentRequest?.removeAllListeners(); - }; - }, [ - setExpressPaymentError, - paymentRequest, - api, - setIsFinished, - setPaymentRequest, - onClose, - cartData, - ] ); - - return { - paymentRequest, - onButtonClick, - paymentRequestType, - }; -}; diff --git a/client/tokenized-payment-request/button-ui.js b/client/tokenized-payment-request/button-ui.js deleted file mode 100644 index b0ca818d213..00000000000 --- a/client/tokenized-payment-request/button-ui.js +++ /dev/null @@ -1,47 +0,0 @@ -/* global jQuery */ - -let $wcpayPaymentRequestContainer = null; - -const paymentRequestButtonUi = { - init: ( { $container } ) => { - $wcpayPaymentRequestContainer = $container; - }, - - getElements: () => { - return jQuery( - '.wcpay-express-checkout-wrapper,#wcpay-express-checkout-button-separator' - ); - }, - - blockButton: () => { - // check if element isn't already blocked before calling block() to avoid blinking overlay issues - // blockUI.isBlocked is either undefined or 0 when element is not blocked - if ( $wcpayPaymentRequestContainer.data( 'blockUI.isBlocked' ) ) { - return; - } - - $wcpayPaymentRequestContainer.block( { message: null } ); - }, - - unblockButton: () => { - paymentRequestButtonUi.show(); - $wcpayPaymentRequestContainer.unblock(); - }, - - showButton: ( paymentRequestButton ) => { - if ( $wcpayPaymentRequestContainer.length ) { - paymentRequestButtonUi.show(); - paymentRequestButton.mount( '#wcpay-payment-request-button' ); - } - }, - - hide: () => { - paymentRequestButtonUi.getElements().hide(); - }, - - show: () => { - paymentRequestButtonUi.getElements().show(); - }, -}; - -export default paymentRequestButtonUi; diff --git a/client/tokenized-payment-request/event-handlers.js b/client/tokenized-payment-request/event-handlers.js deleted file mode 100644 index 872f51c86b3..00000000000 --- a/client/tokenized-payment-request/event-handlers.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * External dependencies - */ -import { applyFilters } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import { - transformStripePaymentMethodForStoreApi, - transformStripeShippingAddressForStoreApi, -} from './transformers/stripe-to-wc'; -import { - transformCartDataForDisplayItems, - transformCartDataForShippingOptions, - transformPrice, -} from './transformers/wc-to-stripe'; - -import { - getPaymentRequestData, - getErrorMessageFromNotice, -} from './frontend-utils'; - -import PaymentRequestCartApi from './cart-api'; - -const cartApi = new PaymentRequestCartApi(); - -export const shippingAddressChangeHandler = async ( event ) => { - try { - // Please note that the `event.shippingAddress` might not contain all the fields. - // Some fields might not be present (like `line_1` or `line_2`) due to semi-anonymized data. - const cartData = await cartApi.updateCustomer( - transformStripeShippingAddressForStoreApi( event.shippingAddress ) - ); - - const shippingOptions = transformCartDataForShippingOptions( cartData ); - - // when no shipping options are returned, the API still returns a 200 status code. - // We need to ensure that shipping options are present - otherwise the PRB dialog won't update correctly. - if ( shippingOptions.length === 0 ) { - event.updateWith( { - // Possible statuses: https://docs.stripe.com/js/appendix/payment_response#payment_response_object-complete - status: 'invalid_shipping_address', - } ); - - return; - } - - event.updateWith( { - // Possible statuses: https://docs.stripe.com/js/appendix/payment_response#payment_response_object-complete - status: 'success', - shippingOptions: transformCartDataForShippingOptions( cartData ), - total: { - label: getPaymentRequestData( 'total_label' ), - amount: transformPrice( - parseInt( cartData.totals.total_price, 10 ) - - parseInt( cartData.totals.total_refund || 0, 10 ), - cartData.totals - ), - }, - displayItems: transformCartDataForDisplayItems( cartData ), - } ); - } catch ( error ) { - // Possible statuses: https://docs.stripe.com/js/appendix/payment_response#payment_response_object-complete - event.updateWith( { - status: 'fail', - } ); - } -}; - -export const shippingOptionChangeHandler = async ( event ) => { - try { - const cartData = await cartApi.selectShippingRate( { - package_id: 0, - rate_id: event.shippingOption.id, - } ); - - event.updateWith( { - status: 'success', - total: { - label: getPaymentRequestData( 'total_label' ), - amount: transformPrice( - parseInt( cartData.totals.total_price, 10 ) - - parseInt( cartData.totals.total_refund || 0, 10 ), - cartData.totals - ), - }, - displayItems: transformCartDataForDisplayItems( cartData ), - } ); - } catch ( error ) { - event.updateWith( { status: 'fail' } ); - } -}; - -const paymentResponseHandler = async ( - api, - response, - completePayment, - abortPayment, - event -) => { - if ( response.payment_result.payment_status !== 'success' ) { - return abortPayment( - event, - getErrorMessageFromNotice( - response.message || - response.payment_result?.payment_details.find( - ( detail ) => detail.key === 'errorMessage' - )?.value - ) - ); - } - - try { - const confirmationRequest = api.confirmIntent( - response.payment_result.redirect_url - ); - // We need to call `complete` outside of `completePayment` to close the dialog for 3DS. - event.complete( 'success' ); - - // `true` means there is no intent to confirm. - if ( confirmationRequest === true ) { - completePayment( response.payment_result.redirect_url ); - } else { - const redirectUrl = await confirmationRequest; - - completePayment( redirectUrl ); - } - } catch ( error ) { - abortPayment( - event, - getErrorMessageFromNotice( - error.message || - error.payment_result?.payment_details.find( - ( detail ) => detail.key === 'errorMessage' - )?.value - ) - ); - } -}; - -export const paymentMethodHandler = async ( - api, - completePayment, - abortPayment, - event -) => { - try { - // Kick off checkout processing step. - const response = await cartApi.placeOrder( { - // adding extension data as a separate action, - // so that we make it harder for external plugins to modify or intercept checkout data. - ...transformStripePaymentMethodForStoreApi( event ), - extensions: applyFilters( - 'wcpay.payment-request.cart-place-order-extension-data', - {} - ), - } ); - - paymentResponseHandler( - api, - response, - completePayment, - abortPayment, - event - ); - } catch ( error ) { - abortPayment( - event, - getErrorMessageFromNotice( - error.message || - error.payment_result?.payment_details.find( - ( detail ) => detail.key === 'errorMessage' - )?.value - ) - ); - } -}; diff --git a/client/tokenized-payment-request/frontend-utils.js b/client/tokenized-payment-request/frontend-utils.js deleted file mode 100644 index c1dd1d4d46c..00000000000 --- a/client/tokenized-payment-request/frontend-utils.js +++ /dev/null @@ -1,257 +0,0 @@ -/* global wcpayPaymentRequestParams */ - -/** - * Internal dependencies - */ -import { - transformCartDataForDisplayItems, - transformPrice, -} from './transformers/wc-to-stripe'; - -/** - * Retrieves payment request data from global variable. - * - * @param {string} key The object property key. - * @return {mixed} Value of the object prop or null. - */ -export const getPaymentRequestData = ( key ) => { - if ( - typeof wcpayPaymentRequestParams === 'object' && - wcpayPaymentRequestParams.hasOwnProperty( key ) - ) { - return wcpayPaymentRequestParams[ key ]; - } - return null; -}; - -/** - * Returns a Stripe payment request object. - * - * @param {Object} config A configuration object for getting the payment request. - * @return {Object} Payment Request options object - */ -export const getPaymentRequest = ( { stripe, cartData, productData } ) => { - // the country code defined here comes from the WC settings. - // It might be interesting to ensure the country code coincides with the Stripe account's country, - // as defined here: https://docs.stripe.com/js/payment_request/create - let country = getPaymentRequestData( 'checkout' )?.country_code; - - // Puerto Rico (PR) is the only US territory/possession that's supported by Stripe. - // Since it's considered a US state by Stripe, we need to do some special mapping. - if ( country === 'PR' ) { - country = 'US'; - } - - return stripe.paymentRequest( { - country, - requestPayerName: true, - requestPayerEmail: true, - requestPayerPhone: getPaymentRequestData( 'checkout' ) - ?.needs_payer_phone, - ...( productData - ? { - // we can't just pass `productData`, and we need a little bit of massaging for older data. - currency: productData.currency, - total: productData.total, - displayItems: productData.displayItems, - requestShipping: productData.needs_shipping, - } - : { - currency: cartData.totals.currency_code.toLowerCase(), - total: { - label: getPaymentRequestData( 'total_label' ), - amount: transformPrice( - parseInt( cartData.totals.total_price, 10 ) - - parseInt( - cartData.totals.total_refund || 0, - 10 - ), - cartData.totals - ), - }, - requestShipping: - getPaymentRequestData( 'button_context' ) === - 'pay_for_order' - ? false - : cartData.needs_shipping, - displayItems: transformCartDataForDisplayItems( cartData ), - } ), - } ); -}; - -/** - * Utility function for updating the Stripe PaymentRequest object - * - * @param {Object} update An object containing the things needed for the update. - */ -export const updatePaymentRequest = ( { paymentRequest, cartData } ) => { - paymentRequest.update( { - total: { - label: getPaymentRequestData( 'total_label' ), - amount: transformPrice( - parseInt( cartData.totals.total_price, 10 ) - - parseInt( cartData.totals.total_refund || 0, 10 ), - cartData.totals - ), - }, - displayItems: transformCartDataForDisplayItems( cartData ), - } ); -}; - -/** - * Displays a `confirm` dialog which leads to a redirect. - * - * @param {string} paymentRequestType Can be either apple_pay, google_pay or payment_request_api. - */ -export const displayLoginConfirmationDialog = ( paymentRequestType ) => { - if ( ! getPaymentRequestData( 'login_confirmation' ) ) { - return; - } - - let message = getPaymentRequestData( 'login_confirmation' )?.message; - - // Replace dialog text with specific payment request type "Apple Pay" or "Google Pay". - message = message.replace( - /\*\*.*?\*\*/, - paymentRequestType === 'apple_pay' ? 'Apple Pay' : 'Google Pay' - ); - - // Remove asterisks from string. - message = message.replace( /\*\*/g, '' ); - - if ( confirm( message ) ) { - // Redirect to my account page. - window.location.href = getPaymentRequestData( - 'login_confirmation' - )?.redirect_url; - } -}; - -/** - * Parses HTML error notice and returns single error message. - * - * @param {string} notice Error notice DOM HTML. - * @return {string} Error message content - */ -export const getErrorMessageFromNotice = ( notice ) => { - const div = document.createElement( 'div' ); - div.innerHTML = notice.trim(); - return div.firstChild ? div.firstChild.textContent : ''; -}; - -/** - * Searches object for matching key and returns corresponding property value from matched item. - * - * @param {Object} obj Object to search for key. - * @param {string} key Key to match in object. - * @param {string} property Property in object to return correct value. - * @return {int|null} Value to return - */ -const getPropertyByKey = ( obj, key, property ) => { - const foundItem = obj.find( ( item ) => item.key === key ); - return foundItem ? foundItem[ property ] : null; -}; - -/** - * Transforms totals from cartDataContent into format expected by the Store API. - * - * @param {Object} cartDataContent cartData from content component - * @return {Object} Cart totals object for Store API - */ -const constructCartDataContentTotals = ( cartDataContent ) => { - const totals = { - total_items: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_items', - 'value' - )?.toString(), - total_items_tax: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_tax', - 'value' - )?.toString(), - total_fees: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_fees', - 'value' - )?.toString(), - total_fees_tax: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_fees', - 'valueWithTax' - )?.toString(), - total_discount: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_discount', - 'value' - )?.toString(), - total_discount_tax: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_discount', - 'valueWithTax' - )?.toString(), - total_shipping: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_shipping', - 'value' - )?.toString(), - total_shipping_tax: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_shipping', - 'valueWithTax' - )?.toString(), - total_price: cartDataContent.cartTotal.value.toString(), - total_tax: getPropertyByKey( - cartDataContent.cartTotalItems, - 'total_tax', - 'value' - )?.toString(), - currency_code: cartDataContent.currency.code, - currency_symbol: cartDataContent.currency.symbol, - currency_minor_unit: cartDataContent.currency.minorUnit, - currency_decimal_separator: cartDataContent.currency.decimalSeparator, - currency_thousand_separator: cartDataContent.currency.thousandSeparator, - currency_prefix: cartDataContent.currency.prefix, - currency_suffix: cartDataContent.currency.suffix, - }; - - return totals; -}; - -/** - * Transforms the cartData object to the format expected by the Store API. cartData is coming to the blocks Payment Request method - * in two different formats: from the canMakePayment function and from the content component. This function takes in either format - * and transforms it into the format expected by the Store API. - * - * @param {Object|null} cartDataCanMakePayment cartData from canMakePayment function. - * @param {Object|null} cartDataContent cartData from content component. - * @return {Object} Cart totals object. - */ -export const transformCartDataForStoreAPI = ( - cartDataCanMakePayment, - cartDataContent -) => { - let mappedCartData = {}; - - if ( cartDataCanMakePayment ) { - mappedCartData = { - ...cartDataCanMakePayment, - items: cartDataCanMakePayment.cart.cartItems, - totals: cartDataCanMakePayment.cartTotals, - needs_shipping: cartDataCanMakePayment.cartNeedsShipping, - shipping_rates: cartDataCanMakePayment.cart.shippingRates, - }; - } - - if ( cartDataContent ) { - mappedCartData = { - items: cartDataContent.cartItems, - totals: constructCartDataContentTotals( cartDataContent ), - needs_shipping: cartDataContent.needsShipping, - shipping_rates: cartDataContent.shippingRates, - extensions: cartDataContent.extensions, - }; - } - - return mappedCartData; -}; diff --git a/client/tokenized-payment-request/index.js b/client/tokenized-payment-request/index.js deleted file mode 100644 index f1797b725e2..00000000000 --- a/client/tokenized-payment-request/index.js +++ /dev/null @@ -1,90 +0,0 @@ -/* global jQuery */ -/** - * External dependencies - */ -import { applyFilters } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import { getUPEConfig } from 'wcpay/utils/checkout'; -import WCPayAPI from '../checkout/api'; -import PaymentRequestCartApi from './cart-api'; -import PaymentRequestOrderApi from './order-api'; -import WooPaymentsPaymentRequest from './payment-request'; -import paymentRequestButtonUi from './button-ui'; -import { getPaymentRequestData } from './frontend-utils'; -import './compatibility/wc-deposits'; -import './compatibility/wc-order-attribution'; -import './compatibility/wc-product-variations'; - -import '../checkout/express-checkout-buttons.scss'; - -jQuery( ( $ ) => { - // Don't load if blocks checkout is being loaded. - if ( - getPaymentRequestData( 'has_block' ) && - getPaymentRequestData( 'button_context' ) !== 'pay_for_order' - ) { - return; - } - - const publishableKey = getPaymentRequestData( 'stripe' ).publishableKey; - - if ( ! publishableKey ) { - // If no configuration is present, we can't do anything. - return; - } - - // initializing the UI's container. - paymentRequestButtonUi.init( { - $container: $( '#wcpay-payment-request-button' ), - } ); - - const api = new WCPayAPI( - { - publishableKey, - accountId: getPaymentRequestData( 'stripe' ).accountId, - locale: getPaymentRequestData( 'stripe' ).locale, - }, - // A promise-based interface to jQuery.post. - ( url, args ) => { - return new Promise( ( resolve, reject ) => { - $.post( url, args ).then( resolve ).fail( reject ); - } ); - } - ); - let paymentRequestCartApi = new PaymentRequestCartApi(); - if ( getPaymentRequestData( 'button_context' ) === 'pay_for_order' ) { - paymentRequestCartApi = new PaymentRequestOrderApi( { - orderId: getUPEConfig( 'order_id' ), - key: getUPEConfig( 'key' ), - billingEmail: getUPEConfig( 'billing_email' ), - } ); - } - - const wooPaymentsPaymentRequest = new WooPaymentsPaymentRequest( { - wcpayApi: api, - paymentRequestCartApi, - productData: getPaymentRequestData( 'product' ) || undefined, - } ); - - wooPaymentsPaymentRequest.init(); - - // When the cart is updated, the PRB is removed from the page and needs to be re-initialized. - $( document.body ).on( 'updated_cart_totals', async () => { - await applyFilters( - 'wcpay.payment-request.update-button-data', - Promise.resolve() - ); - wooPaymentsPaymentRequest.init(); - } ); - - // We need to refresh payment request data when total is updated. - $( document.body ).on( 'updated_checkout', async () => { - await applyFilters( - 'wcpay.payment-request.update-button-data', - Promise.resolve() - ); - } ); -} ); diff --git a/client/tokenized-payment-request/payment-request.js b/client/tokenized-payment-request/payment-request.js deleted file mode 100644 index 046769cfd7d..00000000000 --- a/client/tokenized-payment-request/payment-request.js +++ /dev/null @@ -1,478 +0,0 @@ -/* global jQuery */ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { - doAction, - applyFilters, - removeFilter, - addFilter, -} from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import { - setPaymentRequestBranding, - trackPaymentRequestButtonClick, - trackPaymentRequestButtonLoad, -} from './tracking'; -import { - transformStripePaymentMethodForStoreApi, - transformStripeShippingAddressForStoreApi, -} from './transformers/stripe-to-wc'; -import { - transformCartDataForDisplayItems, - transformCartDataForShippingOptions, - transformPrice, -} from './transformers/wc-to-stripe'; -import paymentRequestButtonUi from './button-ui'; -import { - getPaymentRequest, - displayLoginConfirmationDialog, - getPaymentRequestData, -} from './frontend-utils'; -import PaymentRequestCartApi from './cart-api'; -import debounce from './debounce'; - -const noop = () => null; - -/** - * Class to handle Stripe payment forms. - */ -export default class WooPaymentsPaymentRequest { - /** - * Whether the payment was aborted by the customer. - */ - isPaymentAborted = false; - - /** - * Whether global listeners have been added. - */ - areListenersInitialized = false; - - /** - * The cart data represented if the product were to be added to the cart (or, on cart/checkout pages, the cart data itself). - * This is useful on product pages to understand if shipping is needed. - */ - cachedCartData = undefined; - - /** - * API to interface with the cart. - * - * @type {PaymentRequestCartApi} - */ - paymentRequestCartApi = undefined; - - /** - * WCPayAPI instance. - * - * @type {WCPayAPI} - */ - wcpayApi = undefined; - - /** - * On page load for product pages, we might get some data from the backend (which might get overwritten later). - */ - initialProductData = undefined; - - constructor( { wcpayApi, paymentRequestCartApi, productData } ) { - this.wcpayApi = wcpayApi; - this.paymentRequestCartApi = paymentRequestCartApi; - this.initialProductData = productData; - } - - /** - * Starts the payment request - */ - async startPaymentRequest() { - // reference to this class' instance, to be used inside callbacks to avoid `this` misunderstandings. - const _self = this; - const stripe = await this.wcpayApi.getStripe(); - const paymentRequest = getPaymentRequest( { - stripe, - cartData: this.cachedCartData, - productData: this.initialProductData, - } ); - - // Check the availability of the Payment Request API first. - const paymentPermissionResult = await paymentRequest.canMakePayment(); - if ( ! paymentPermissionResult ) { - doAction( 'wcpay.payment-request.availability', { - paymentRequestType: null, - } ); - return; - } - - const buttonBranding = paymentPermissionResult.applePay - ? 'apple_pay' - : 'google_pay'; - - doAction( 'wcpay.payment-request.availability', { - paymentRequestType: buttonBranding, - } ); - - setPaymentRequestBranding( buttonBranding ); - trackPaymentRequestButtonLoad( - getPaymentRequestData( 'button_context' ) - ); - - // on product pages, we need to interact with an anonymous cart to checkout the product, - // so that we don't affect the products in the main cart. - // On cart, checkout, place order pages we instead use the cart itself. - if ( getPaymentRequestData( 'button_context' ) === 'product' ) { - this.paymentRequestCartApi.useSeparateCart(); - } - - const paymentRequestButton = stripe - .elements() - .create( 'paymentRequestButton', { - paymentRequest: paymentRequest, - style: { - paymentRequestButton: { - type: getPaymentRequestData( 'button' ).type, - theme: getPaymentRequestData( 'button' ).theme, - height: getPaymentRequestData( 'button' ).height + 'px', - }, - }, - } ); - paymentRequestButtonUi.showButton( paymentRequestButton ); - - if ( getPaymentRequestData( 'button_context' ) === 'pay_for_order' ) { - paymentRequestButton.on( 'click', () => { - trackPaymentRequestButtonClick( 'pay_for_order' ); - } ); - } - - if ( getPaymentRequestData( 'button_context' ) === 'product' ) { - this.attachPaymentRequestButtonEventListeners(); - } - - removeFilter( - 'wcpay.payment-request.update-button-data', - 'automattic/wcpay/payment-request' - ); - addFilter( - 'wcpay.payment-request.update-button-data', - 'automattic/wcpay/payment-request', - async ( previousPromise ) => { - // Wait for previous filters - await previousPromise; - - const newCartData = await _self.getCartData(); - // checking if items needed shipping, before assigning new cart data. - const didItemsNeedShipping = - _self.initialProductData?.needs_shipping || - _self.cachedCartData?.needs_shipping; - - _self.cachedCartData = newCartData; - - /** - * If the customer aborted the payment request, we need to re init the payment request button to ensure the shipping - * options are re-fetched. If the customer didn't abort the payment request, and the product's shipping status is - * consistent, we can simply update the payment request button with the new total and display items. - */ - if ( - ! _self.isPaymentAborted && - didItemsNeedShipping === newCartData.needs_shipping - ) { - paymentRequest.update( { - total: { - label: getPaymentRequestData( 'total_label' ), - amount: transformPrice( - parseInt( newCartData.totals.total_price, 10 ) - - parseInt( - newCartData.totals.total_refund || 0, - 10 - ), - newCartData.totals - ), - }, - displayItems: transformCartDataForDisplayItems( - newCartData - ), - } ); - } else { - await _self.init(); - } - } - ); - - if ( getPaymentRequestData( 'button_context' ) === 'product' ) { - const $addToCartButton = jQuery( '.single_add_to_cart_button' ); - - paymentRequestButton.on( 'click', ( event ) => { - trackPaymentRequestButtonClick( 'product' ); - - // If login is required for checkout, display redirect confirmation dialog. - if ( getPaymentRequestData( 'login_confirmation' ) ) { - event.preventDefault(); - displayLoginConfirmationDialog( buttonBranding ); - return; - } - - // First check if product can be added to cart. - if ( $addToCartButton.is( '.disabled' ) ) { - event.preventDefault(); // Prevent showing payment request modal. - if ( - $addToCartButton.is( '.wc-variation-is-unavailable' ) - ) { - window.alert( - window.wc_add_to_cart_variation_params - ?.i18n_unavailable_text || - __( - 'Sorry, this product is unavailable. Please choose a different combination.', - 'woocommerce-payments' - ) - ); - } else { - window.alert( - window?.wc_add_to_cart_variation_params - ?.i18n_make_a_selection_text || - __( - 'Please select some product options before adding this product to your cart.', - 'woocommerce-payments' - ) - ); - } - return; - } - - _self.paymentRequestCartApi.addProductToCart(); - } ); - } - - paymentRequest.on( 'cancel', () => { - _self.isPaymentAborted = true; - - if ( getPaymentRequestData( 'button_context' ) === 'product' ) { - // clearing the cart to avoid issues with products with low or limited availability - // being held hostage by customers cancelling the PRB. - _self.paymentRequestCartApi.emptyCart(); - } - } ); - - paymentRequest.on( 'shippingaddresschange', async ( event ) => { - try { - // Please note that the `event.shippingAddress` might not contain all the fields. - // Some fields might not be present (like `line_1` or `line_2`) due to semi-anonymized data. - const cartData = await _self.paymentRequestCartApi.updateCustomer( - transformStripeShippingAddressForStoreApi( - event.shippingAddress - ) - ); - - const shippingOptions = transformCartDataForShippingOptions( - cartData - ); - - // when no shipping options are returned, the API still returns a 200 status code. - // We need to ensure that shipping options are present - otherwise the PRB dialog won't update correctly. - if ( shippingOptions.length === 0 ) { - event.updateWith( { - // Possible statuses: https://docs.stripe.com/js/appendix/payment_response#payment_response_object-complete - status: 'invalid_shipping_address', - } ); - _self.cachedCartData = cartData; - - return; - } - - event.updateWith( { - // Possible statuses: https://docs.stripe.com/js/appendix/payment_response#payment_response_object-complete - status: 'success', - shippingOptions, - total: { - label: getPaymentRequestData( 'total_label' ), - amount: transformPrice( - parseInt( cartData.totals.total_price, 10 ) - - parseInt( - cartData.totals.total_refund || 0, - 10 - ), - cartData.totals - ), - }, - displayItems: transformCartDataForDisplayItems( cartData ), - } ); - - _self.cachedCartData = cartData; - } catch ( error ) { - // Possible statuses: https://docs.stripe.com/js/appendix/payment_response#payment_response_object-complete - event.updateWith( { - status: 'fail', - } ); - } - } ); - - paymentRequest.on( 'shippingoptionchange', async ( event ) => { - try { - const cartData = await _self.paymentRequestCartApi.selectShippingRate( - { package_id: 0, rate_id: event.shippingOption.id } - ); - - event.updateWith( { - status: 'success', - total: { - label: getPaymentRequestData( 'total_label' ), - amount: transformPrice( - parseInt( cartData.totals.total_price, 10 ) - - parseInt( - cartData.totals.total_refund || 0, - 10 - ), - cartData.totals - ), - }, - displayItems: transformCartDataForDisplayItems( cartData ), - } ); - _self.cachedCartData = cartData; - } catch ( error ) { - event.updateWith( { status: 'fail' } ); - } - } ); - - paymentRequest.on( 'paymentmethod', async ( event ) => { - // TODO: this works for PDPs - need to handle checkout scenarios for cart, checkout. - try { - const response = await _self.paymentRequestCartApi.placeOrder( { - // adding extension data as a separate action, - // so that we make it harder for external plugins to modify or intercept checkout data. - ...transformStripePaymentMethodForStoreApi( event ), - extensions: applyFilters( - 'wcpay.payment-request.cart-place-order-extension-data', - {} - ), - } ); - - const confirmationRequest = _self.wcpayApi.confirmIntent( - response.payment_result.redirect_url - ); - // We need to call `complete` before redirecting to close the dialog for 3DS. - event.complete( 'success' ); - - let redirectUrl = ''; - - // `true` means there is no intent to confirm. - if ( confirmationRequest === true ) { - redirectUrl = response.payment_result.redirect_url; - } else { - redirectUrl = await confirmationRequest; - } - - jQuery.blockUI( { - message: null, - overlayCSS: { - background: '#fff', - opacity: 0.6, - }, - } ); - - window.location = redirectUrl; - } catch ( error ) { - const response = await error.json(); - event.complete( 'fail' ); - - jQuery( '.woocommerce-error' ).remove(); - - const $container = jQuery( - '.woocommerce-notices-wrapper' - ).first(); - - // the error thrown could have different formats, depending if it was a Store API failure or an ajax failure. - const errorMessage = - response.message || - response.payment_result?.payment_details.find( - ( detail ) => detail.key === 'errorMessage' - )?.value; - if ( $container.length ) { - $container.append( - jQuery( '
' ).text( - errorMessage - ) - ); - - jQuery( 'html, body' ).animate( - { - scrollTop: $container - .find( '.woocommerce-error' ) - .offset().top, - }, - 600 - ); - } - } - } ); - } - - attachPaymentRequestButtonEventListeners() { - if ( this.areListenersInitialized ) { - return; - } - - this.areListenersInitialized = true; - // Block the payment request button as soon as an "input" event is fired, to avoid sync issues - // when the customer clicks on the button before the debounced event is processed. - const $quantityInput = jQuery( '.quantity' ); - const handleQuantityChange = () => { - paymentRequestButtonUi.blockButton(); - }; - $quantityInput.on( 'input', '.qty', handleQuantityChange ); - $quantityInput.on( - 'input', - '.qty', - debounce( 250, async () => { - await applyFilters( - 'wcpay.payment-request.update-button-data', - Promise.resolve() - ); - paymentRequestButtonUi.unblockButton(); - } ) - ); - } - - async getCartData() { - if ( getPaymentRequestData( 'button_context' ) !== 'product' ) { - return await this.paymentRequestCartApi.getCart(); - } - - // creating a new cart and clearing it afterwards, - // to avoid scenarios where the stock for a product with limited (or low) availability is added to the cart, - // preventing other customers from purchasing. - const temporaryCart = new PaymentRequestCartApi(); - temporaryCart.useSeparateCart(); - - const cartData = await temporaryCart.addProductToCart(); - - // no need to wait for the request to end, it can be done asynchronously. - // using `.finally( noop )` to avoid annoying IDE warnings. - temporaryCart.emptyCart().finally( noop ); - - return cartData; - } - - /** - * Initialize event handlers and UI state - */ - async init() { - // on product pages, we should be able to have `initialProductData` from the backend - which saves us some AJAX calls. - if ( ! this.cachedCartData && ! this.initialProductData ) { - try { - this.cachedCartData = await this.getCartData(); - } catch ( e ) { - // if something fails here, we can likely fall back on the `initialProductData`. - } - } - - // once (and if) cart data has been fetched, we can safely clear cached product data. - if ( this.cachedCartData ) { - this.initialProductData = undefined; - } - - await this.startPaymentRequest(); - - // After initializing a new payment request, we need to reset the isPaymentAborted flag. - this.isPaymentAborted = false; - } -} diff --git a/client/tokenized-payment-request/test/payment-request.test.js b/client/tokenized-payment-request/test/payment-request.test.js deleted file mode 100644 index 617384976e5..00000000000 --- a/client/tokenized-payment-request/test/payment-request.test.js +++ /dev/null @@ -1,158 +0,0 @@ -/** - * External dependencies - */ -import apiFetch from '@wordpress/api-fetch'; -import { addAction, applyFilters, doAction } from '@wordpress/hooks'; - -/** - * Internal dependencies - */ -import PaymentRequestCartApi from '../cart-api'; -import WooPaymentsPaymentRequest from '../payment-request'; -import { trackPaymentRequestButtonLoad } from '../tracking'; - -jest.mock( '@wordpress/api-fetch', () => jest.fn() ); -jest.mock( '../tracking', () => ( { - setPaymentRequestBranding: () => null, - trackPaymentRequestButtonClick: () => null, - trackPaymentRequestButtonLoad: jest.fn(), -} ) ); - -jest.mock( '../button-ui', () => ( { - showButton: () => null, - blockButton: () => null, - unblockButton: () => null, -} ) ); -jest.mock( '../debounce', () => ( wait, func ) => - function () { - func.apply( this, arguments ); - } -); - -const jQueryMock = ( selector ) => { - if ( typeof selector === 'function' ) { - return selector( jQueryMock ); - } - - return { - on: ( event, callbackOrSelector, callback2 ) => - addAction( - `payment-request-test.jquery-event.${ selector }${ - typeof callbackOrSelector === 'string' - ? `.${ callbackOrSelector }` - : '' - }.${ event }`, - 'tests', - typeof callbackOrSelector === 'string' - ? callback2 - : callbackOrSelector - ), - val: () => null, - is: () => null, - remove: () => null, - }; -}; -jQueryMock.blockUI = () => null; - -describe( 'WooPaymentsPaymentRequest', () => { - let wcpayApi; - - beforeEach( () => { - global.$ = jQueryMock; - global.jQuery = jQueryMock; - global.wcpayPaymentRequestParams = { - nonce: { - store_api_nonce: 'global_store_api_nonce', - }, - button_context: 'cart', - checkout: { - needs_payer_phone: true, - country_code: 'US', - currency_code: 'usd', - }, - total_label: 'wcpay.test (via WooCommerce)', - button: { type: 'default', theme: 'dark', height: '48' }, - }; - wcpayApi = { - getStripe: () => ( { - paymentRequest: () => ( { - update: () => null, - canMakePayment: () => ( { googlePay: true } ), - on: ( event, callback ) => - addAction( - `payment-request-test.registered-action.${ event }`, - 'tests', - callback - ), - } ), - elements: () => ( { - create: () => ( { on: () => null } ), - } ), - } ), - }; - } ); - - afterEach( () => { - jest.resetAllMocks(); - } ); - - it( 'should initialize the Stripe payment request, fire initial tracking, and attach event listeners', async () => { - const headers = new Headers(); - headers.append( 'Nonce', 'nonce-value' ); - - apiFetch.mockResolvedValue( { - headers: headers, - json: () => - Promise.resolve( { - needs_shipping: false, - totals: { - currency_code: 'USD', - total_price: '20', - total_tax: '0', - total_shipping: '5', - }, - items: [ - { name: 'Shirt', quantity: 1, prices: { price: '15' } }, - ], - } ), - } ); - const paymentRequestAvailabilityCallback = jest.fn(); - addAction( - 'wcpay.payment-request.availability', - 'test', - paymentRequestAvailabilityCallback - ); - - const cartApi = new PaymentRequestCartApi(); - const paymentRequest = new WooPaymentsPaymentRequest( { - wcpayApi: wcpayApi, - paymentRequestCartApi: cartApi, - } ); - - expect( paymentRequestAvailabilityCallback ).not.toHaveBeenCalled(); - expect( trackPaymentRequestButtonLoad ).not.toHaveBeenCalled(); - - await paymentRequest.init(); - - expect( paymentRequestAvailabilityCallback ).toHaveBeenCalledTimes( 1 ); - expect( paymentRequestAvailabilityCallback ).toHaveBeenCalledWith( - expect.objectContaining( { paymentRequestType: 'google_pay' } ) - ); - expect( trackPaymentRequestButtonLoad ).toHaveBeenCalledWith( 'cart' ); - - await applyFilters( - 'wcpay.payment-request.update-button-data', - Promise.resolve() - ); - expect( paymentRequestAvailabilityCallback ).toHaveBeenCalledTimes( 1 ); - - // firing this should initialize the button again. - doAction( 'payment-request-test.registered-action.cancel' ); - - await applyFilters( - 'wcpay.payment-request.update-button-data', - Promise.resolve() - ); - expect( paymentRequestAvailabilityCallback ).toHaveBeenCalledTimes( 2 ); - } ); -} ); diff --git a/client/tokenized-payment-request/tracking.js b/client/tokenized-payment-request/tracking.js deleted file mode 100644 index 403160a80fe..00000000000 --- a/client/tokenized-payment-request/tracking.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * External dependencies - */ -import { debounce } from 'lodash'; -import { recordUserEvent } from 'tracks'; - -let paymentRequestBranding; - -// Track the payment request button click event. -export const trackPaymentRequestButtonClick = ( source ) => { - const paymentRequestTypeEvents = { - google_pay: 'gpay_button_click', - apple_pay: 'applepay_button_click', - }; - - const event = paymentRequestTypeEvents[ paymentRequestBranding ]; - if ( ! event ) return; - - recordUserEvent( event, { source } ); -}; - -// Track the payment request button load event. -export const trackPaymentRequestButtonLoad = debounce( ( source ) => { - const paymentRequestTypeEvents = { - google_pay: 'gpay_button_load', - apple_pay: 'applepay_button_load', - }; - - const event = paymentRequestTypeEvents[ paymentRequestBranding ]; - if ( ! event ) return; - - recordUserEvent( event, { source } ); -}, 1000 ); - -export const setPaymentRequestBranding = ( branding ) => - ( paymentRequestBranding = branding ); diff --git a/client/tokenized-payment-request/transformers/stripe-to-wc.js b/client/tokenized-payment-request/transformers/stripe-to-wc.js deleted file mode 100644 index e8902213c33..00000000000 --- a/client/tokenized-payment-request/transformers/stripe-to-wc.js +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Transform shipping address information from Stripe's address object to - * the cart shipping address object shape. - * - * @param {Object} shippingAddress Stripe's shipping address item - * - * @return {Object} The shipping address in the shape expected by the cart. - */ -export const transformStripeShippingAddressForStoreApi = ( - shippingAddress -) => { - return { - shipping_address: { - first_name: - shippingAddress.recipient - ?.split( ' ' ) - ?.slice( 0, 1 ) - ?.join( ' ' ) ?? '', - last_name: - shippingAddress.recipient - ?.split( ' ' ) - ?.slice( 1 ) - ?.join( ' ' ) ?? '', - company: shippingAddress.organization ?? '', - address_1: shippingAddress.addressLine?.[ 0 ] ?? '', - address_2: shippingAddress.addressLine?.[ 1 ] ?? '', - city: shippingAddress.city ?? '', - state: shippingAddress.region ?? '', - country: shippingAddress.country ?? '', - postcode: shippingAddress.postalCode?.replace( ' ', '' ) ?? '', - }, - }; -}; - -/** - * Transform order data from Stripe's object to the expected format for WC. - * - * @param {Object} paymentData Stripe's order object. - * - * @return {Object} Order object in the format WooCommerce expects. - */ -export const transformStripePaymentMethodForStoreApi = ( paymentData ) => { - const name = - ( paymentData.paymentMethod?.billing_details?.name ?? - paymentData.payerName ) || - ''; - const billing = paymentData.paymentMethod?.billing_details?.address ?? {}; - const shipping = paymentData.shippingAddress ?? {}; - - const paymentRequestType = - paymentData.walletName === 'applePay' ? 'apple_pay' : 'google_pay'; - - const billingPhone = - paymentData.paymentMethod?.billing_details?.phone ?? - paymentData.payerPhone?.replace( '/[() -]/g', '' ) ?? - ''; - return { - customer_note: paymentData.order_comments, - billing_address: { - first_name: name.split( ' ' )?.slice( 0, 1 )?.join( ' ' ) ?? '', - last_name: name.split( ' ' )?.slice( 1 )?.join( ' ' ) || '-', - company: billing.organization ?? '', - address_1: billing.line1 ?? '', - address_2: billing.line2 ?? '', - city: billing.city ?? '', - state: billing.state ?? '', - postcode: billing.postal_code ?? '', - country: billing.country ?? '', - email: - paymentData.paymentMethod?.billing_details?.email ?? - paymentData.payerEmail ?? - '', - phone: billingPhone, - }, - // refreshing any shipping address data, now that the customer is placing the order. - shipping_address: { - ...transformStripeShippingAddressForStoreApi( shipping ) - .shipping_address, - // adding the phone number, because it might be needed. - // Stripe doesn't provide us with a different phone number for shipping, so we're going to use the same phone used for billing. - phone: billingPhone, - }, - payment_method: 'woocommerce_payments', - payment_data: [ - { - key: 'payment_method', - value: 'card', - }, - { - key: 'payment_request_type', - value: paymentRequestType, - }, - { - key: 'wcpay-fraud-prevention-token', - value: window.wcpayFraudPreventionToken ?? '', - }, - { - key: 'wcpay-payment-method', - value: paymentData.paymentMethod?.id, - }, - ], - }; -}; diff --git a/includes/class-wc-payments-payment-request-button-handler.php b/includes/class-wc-payments-payment-request-button-handler.php deleted file mode 100644 index 82b33593008..00000000000 --- a/includes/class-wc-payments-payment-request-button-handler.php +++ /dev/null @@ -1,873 +0,0 @@ -account = $account; - $this->gateway = $gateway; - $this->express_checkout_helper = $express_checkout_helper; - } - - /** - * Initialize hooks. - * - * @return void - */ - public function init() { - // Checks if WCPay is enabled. - if ( ! $this->gateway->is_enabled() ) { - return; - } - - if ( ! WC_Payments_Features::is_tokenized_cart_ece_enabled() ) { - return; - } - - // Checks if Payment Request is enabled. - if ( 'yes' !== $this->gateway->get_option( 'payment_request' ) ) { - return; - } - - // Don't load for change payment method page. - if ( isset( $_GET['change_payment_method'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification - return; - } - - add_action( 'template_redirect', [ $this, 'set_session' ] ); - add_action( 'template_redirect', [ $this, 'handle_payment_request_redirect' ] ); - add_action( 'wp_enqueue_scripts', [ $this, 'scripts' ] ); - - add_filter( 'woocommerce_gateway_title', [ $this, 'filter_gateway_title' ], 10, 2 ); - add_action( 'woocommerce_checkout_order_processed', [ $this, 'add_order_meta' ], 10, 2 ); - add_filter( 'woocommerce_login_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 ); - add_filter( 'woocommerce_registration_redirect', [ $this, 'get_login_redirect_url' ], 10, 3 ); - add_filter( 'woocommerce_cart_needs_shipping_address', [ $this, 'filter_cart_needs_shipping_address' ], 11, 1 ); - - // Add a filter for the value of `wcpay_is_apple_pay_enabled`. - // This option does not get stored in the database at all, and this function - // will be used to calculate it whenever the option value is retrieved instead. - // It's used for displaying inbox notifications. - add_filter( 'pre_option_wcpay_is_apple_pay_enabled', [ $this, 'get_option_is_apple_pay_enabled' ], 10, 1 ); - } - - /** - * Checks whether authentication is required for checkout. - * - * @return bool - */ - public function is_authentication_required() { - // If guest checkout is disabled and account creation is not possible, authentication is required. - if ( 'no' === get_option( 'woocommerce_enable_guest_checkout', 'yes' ) && ! $this->is_account_creation_possible() ) { - return true; - } - // If cart contains subscription and account creation is not posible, authentication is required. - if ( $this->has_subscription_product() && ! $this->is_account_creation_possible() ) { - return true; - } - - return false; - } - - /** - * Checks whether account creation is possible during checkout. - * - * @return bool - */ - public function is_account_creation_possible() { - $is_signup_from_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_and_login_from_checkout', 'no' ); - - // If a subscription is being purchased, check if account creation is allowed for subscriptions. - if ( ! $is_signup_from_checkout_allowed && $this->has_subscription_product() ) { - $is_signup_from_checkout_allowed = 'yes' === get_option( 'woocommerce_enable_signup_from_checkout_for_subscriptions', 'no' ); - } - - // If automatically generate username/password are disabled, the Payment Request API - // can't include any of those fields, so account creation is not possible. - return ( - $is_signup_from_checkout_allowed && - 'yes' === get_option( 'woocommerce_registration_generate_username', 'yes' ) && - 'yes' === get_option( 'woocommerce_registration_generate_password', 'yes' ) - ); - } - - /** - * Sets the WC customer session if one is not set. - * This is needed so nonces can be verified by AJAX Request. - * - * @return void - */ - public function set_session() { - // Don't set session cookies on product pages to allow for caching when payment request - // buttons are disabled. But keep cookies if there is already an active WC session in place. - if ( - ! ( $this->express_checkout_helper->is_product() && $this->should_show_payment_request_button() ) - || ( isset( WC()->session ) && WC()->session->has_session() ) - ) { - return; - } - - WC()->session->set_customer_session_cookie( true ); - } - - /** - * Handles payment request redirect when the redirect dialog "Continue" button is clicked. - */ - public function handle_payment_request_redirect() { - if ( - ! empty( $_GET['wcpay_payment_request_redirect_url'] ) - && ! empty( $_GET['_wpnonce'] ) - && wp_verify_nonce( $_GET['_wpnonce'], 'wcpay-set-redirect-url' ) // @codingStandardsIgnoreLine - ) { - $url = rawurldecode( esc_url_raw( wp_unslash( $_GET['wcpay_payment_request_redirect_url'] ) ) ); - // Sets a redirect URL cookie for 10 minutes, which we will redirect to after authentication. - // Users will have a 10 minute timeout to login/create account, otherwise redirect URL expires. - wc_setcookie( 'wcpay_payment_request_redirect_url', $url, time() + MINUTE_IN_SECONDS * 10 ); - // Redirects to "my-account" page. - wp_safe_redirect( get_permalink( get_option( 'woocommerce_myaccount_page_id' ) ) ); - } - } - - /** - * The settings for the `button` attribute - they depend on the "grouped settings" flag value. - * - * @return array - */ - public function get_button_settings() { - $button_type = $this->gateway->get_option( 'payment_request_button_type' ); - $common_settings = $this->express_checkout_helper->get_common_button_settings(); - $payment_request_button_settings = [ - // Default format is en_US. - 'locale' => apply_filters( 'wcpay_payment_request_button_locale', substr( get_locale(), 0, 2 ) ), - 'branded_type' => 'default' === $button_type ? 'short' : 'long', - ]; - - return array_merge( $common_settings, $payment_request_button_settings ); - } - - /** - * Gets the product total price. - * - * @param object $product WC_Product_* object. - * @param bool $is_deposit Whether customer is paying a deposit. - * @param int $deposit_plan_id The ID of the deposit plan. - * - * @return mixed Total price. - * - * @throws Invalid_Price_Exception Whenever a product has no price. - */ - public function get_product_price( $product, ?bool $is_deposit = null, int $deposit_plan_id = 0 ) { - // If prices should include tax, using tax inclusive price. - if ( $this->express_checkout_helper->cart_prices_include_tax() ) { - $base_price = wc_get_price_including_tax( $product ); - } else { - $base_price = wc_get_price_excluding_tax( $product ); - } - - // If WooCommerce Deposits is active, we need to get the correct price for the product. - if ( class_exists( 'WC_Deposits_Product_Manager' ) && class_exists( 'WC_Deposits_Plans_Manager' ) && WC_Deposits_Product_Manager::deposits_enabled( $product->get_id() ) ) { - // If is_deposit is null, we use the default deposit type for the product. - if ( is_null( $is_deposit ) ) { - $is_deposit = 'deposit' === WC_Deposits_Product_Manager::get_deposit_selected_type( $product->get_id() ); - } - if ( $is_deposit ) { - $deposit_type = WC_Deposits_Product_Manager::get_deposit_type( $product->get_id() ); - $available_plan_ids = WC_Deposits_Plans_Manager::get_plan_ids_for_product( $product->get_id() ); - // Default to first (default) plan if no plan is specified. - if ( 'plan' === $deposit_type && 0 === $deposit_plan_id && ! empty( $available_plan_ids ) ) { - $deposit_plan_id = $available_plan_ids[0]; - } - - // Ensure the selected plan is available for the product. - if ( 0 === $deposit_plan_id || in_array( $deposit_plan_id, $available_plan_ids, true ) ) { - $base_price = WC_Deposits_Product_Manager::get_deposit_amount( $product, $deposit_plan_id, 'display', $base_price ); - } - } - } - - // Add subscription sign-up fees to product price. - $sign_up_fee = 0; - $subscription_types = [ - 'subscription', - 'subscription_variation', - ]; - if ( in_array( $product->get_type(), $subscription_types, true ) && class_exists( 'WC_Subscriptions_Product' ) ) { - // When there is no sign-up fee, `get_sign_up_fee` falls back to an int 0. - $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product ); - } - - if ( ! is_numeric( $base_price ) || ! is_numeric( $sign_up_fee ) ) { - $error_message = sprintf( - // Translators: %d is the numeric ID of the product without a price. - __( 'Express checkout does not support products without prices! Please add a price to product #%d', 'woocommerce-payments' ), - (int) $product->get_id() - ); - throw new Invalid_Price_Exception( - esc_html( $error_message ) - ); - } - - return $base_price + $sign_up_fee; - } - - /** - * Gets the product data for the currently viewed page. - * - * @return mixed Returns false if not on a product page, the product information otherwise. - */ - public function get_product_data() { - if ( ! $this->express_checkout_helper->is_product() ) { - return false; - } - - /** @var WC_Product_Variable $product */ // phpcs:ignore - $product = $this->express_checkout_helper->get_product(); - $currency = get_woocommerce_currency(); - - if ( 'variable' === $product->get_type() || 'variable-subscription' === $product->get_type() ) { - $variation_attributes = $product->get_variation_attributes(); - $attributes = []; - - foreach ( $variation_attributes as $attribute_name => $attribute_values ) { - $attribute_key = 'attribute_' . sanitize_title( $attribute_name ); - - // Passed value via GET takes precedence. Otherwise get the default value for given attribute. - $attributes[ $attribute_key ] = isset( $_GET[ $attribute_key ] ) // phpcs:ignore WordPress.Security.NonceVerification - ? wc_clean( wp_unslash( $_GET[ $attribute_key ] ) ) // phpcs:ignore WordPress.Security.NonceVerification - : $product->get_variation_default_attribute( $attribute_name ); - } - - $data_store = WC_Data_Store::load( 'product' ); - $variation_id = $data_store->find_matching_product_variation( $product, $attributes ); - - if ( ! empty( $variation_id ) ) { - $product = wc_get_product( $variation_id ); - } - } - - try { - $price = $this->get_product_price( $product ); - } catch ( Invalid_Price_Exception $e ) { - Logger::log( $e->getMessage() ); - - return false; - } - - $data = []; - $items = []; - - $items[] = [ - 'label' => $product->get_name(), - 'amount' => WC_Payments_Utils::prepare_amount( $price, $currency ), - ]; - - $total_tax = 0; - foreach ( $this->get_taxes_like_cart( $product, $price ) as $tax ) { - $total_tax += $tax; - - $items[] = [ - 'label' => __( 'Tax', 'woocommerce-payments' ), - 'amount' => WC_Payments_Utils::prepare_amount( $tax, $currency ), - 'pending' => 0 === $tax, - ]; - } - - if ( wc_shipping_enabled() && 0 !== wc_get_shipping_method_count( true ) && $product->needs_shipping() ) { - $items[] = [ - 'label' => __( 'Shipping', 'woocommerce-payments' ), - 'amount' => 0, - 'pending' => true, - ]; - - $data['shippingOptions'] = [ - 'id' => 'pending', - 'label' => __( 'Pending', 'woocommerce-payments' ), - 'detail' => '', - 'amount' => 0, - ]; - } - - $data['displayItems'] = $items; - $data['total'] = [ - 'label' => apply_filters( 'wcpay_payment_request_total_label', $this->express_checkout_helper->get_total_label() ), - 'amount' => WC_Payments_Utils::prepare_amount( $price + $total_tax, $currency ), - 'pending' => true, - ]; - - $data['needs_shipping'] = ( wc_shipping_enabled() && 0 !== wc_get_shipping_method_count( true ) && $product->needs_shipping() ); - $data['currency'] = strtolower( $currency ); - $data['country_code'] = substr( get_option( 'woocommerce_default_country' ), 0, 2 ); - - return apply_filters( 'wcpay_payment_request_product_data', $data, $product ); - } - - /** - * Filters the gateway title to reflect Payment Request type - * - * @param string $title Gateway title. - * @param string $id Gateway ID. - */ - public function filter_gateway_title( $title, $id ) { - if ( 'woocommerce_payments' !== $id || ! is_admin() ) { - return $title; - } - - $order = $this->get_current_order(); - $method_title = is_object( $order ) ? $order->get_payment_method_title() : ''; - - if ( ! empty( $method_title ) ) { - if ( - strpos( $method_title, 'Apple Pay' ) === 0 - || strpos( $method_title, 'Google Pay' ) === 0 - || strpos( $method_title, 'Payment Request' ) === 0 - ) { - return $method_title; - } - } - - return $title; - } - - /** - * Used to get the order in admin edit page. - * - * @return WC_Order|WC_Order_Refund|bool - */ - private function get_current_order() { - global $theorder; - global $post; - - if ( is_object( $theorder ) ) { - return $theorder; - } - - if ( is_object( $post ) ) { - return wc_get_order( $post->ID ); - } - - return false; - } - - /** - * Normalizes postal code in case of redacted data from Apple Pay. - * - * @param string $postcode Postal code. - * @param string $country Country. - */ - public function get_normalized_postal_code( $postcode, $country ) { - /** - * Currently, Apple Pay truncates the UK and Canadian postal codes to the first 4 and 3 characters respectively - * when passing it back from the shippingcontactselected object. This causes WC to invalidate - * the postal code and not calculate shipping zones correctly. - */ - if ( Country_Code::UNITED_KINGDOM === $country ) { - // Replaces a redacted string with something like N1C0000. - return str_pad( preg_replace( '/\s+/', '', $postcode ), 7, '0' ); - } - if ( Country_Code::CANADA === $country ) { - // Replaces a redacted string with something like H3B000. - return str_pad( preg_replace( '/\s+/', '', $postcode ), 6, '0' ); - } - - return $postcode; - } - - /** - * Add needed order meta - * - * @param integer $order_id The order ID. - * - * @return void - */ - public function add_order_meta( $order_id ) { - if ( empty( $_POST['payment_request_type'] ) || ! isset( $_POST['payment_method'] ) || 'woocommerce_payments' !== $_POST['payment_method'] ) { // phpcs:ignore WordPress.Security.NonceVerification - return; - } - - $order = wc_get_order( $order_id ); - - $payment_request_type = wc_clean( wp_unslash( $_POST['payment_request_type'] ) ); // phpcs:ignore WordPress.Security.NonceVerification - - $payment_method_titles = [ - 'apple_pay' => 'Apple Pay', - 'google_pay' => 'Google Pay', - ]; - - $suffix = apply_filters( 'wcpay_payment_request_payment_method_title_suffix', 'WooPayments' ); - if ( ! empty( $suffix ) ) { - $suffix = " ($suffix)"; - } - - $payment_method_title = isset( $payment_method_titles[ $payment_request_type ] ) ? $payment_method_titles[ $payment_request_type ] : 'Payment Request'; - $order->set_payment_method_title( $payment_method_title . $suffix ); - $order->save(); - } - - /** - * Checks whether Payment Request Button should be available on this page. - * - * @return bool - */ - public function should_show_payment_request_button() { - // If account is not connected, then bail. - if ( ! $this->account->is_stripe_connected() ) { - return false; - } - - // If no SSL, bail. - if ( ! WC_Payments::mode()->is_test() && ! is_ssl() ) { - Logger::log( 'Stripe Payment Request live mode requires SSL.' ); - - return false; - } - - // Page not supported. - if ( ! $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_cart() && ! $this->express_checkout_helper->is_checkout() ) { - return false; - } - - // Product page, but not available in settings. - if ( $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_available_at( 'product', self::BUTTON_LOCATIONS ) ) { - return false; - } - - // Checkout page, but not available in settings. - if ( $this->express_checkout_helper->is_checkout() && ! $this->express_checkout_helper->is_available_at( 'checkout', self::BUTTON_LOCATIONS ) ) { - return false; - } - - // Cart page, but not available in settings. - if ( $this->express_checkout_helper->is_cart() && ! $this->express_checkout_helper->is_available_at( 'cart', self::BUTTON_LOCATIONS ) ) { - return false; - } - - // Product page, but has unsupported product type. - if ( $this->express_checkout_helper->is_product() && ! apply_filters( 'wcpay_payment_request_is_product_supported', $this->is_product_supported(), $this->express_checkout_helper->get_product() ) ) { - Logger::log( 'Product page has unsupported product type ( Payment Request button disabled )' ); - - return false; - } - - // Cart has unsupported product type. - if ( ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) && ! $this->has_allowed_items_in_cart() ) { - Logger::log( 'Items in the cart have unsupported product type ( Payment Request button disabled )' ); - - return false; - } - - // Order total doesn't matter for Pay for Order page. Thus, this page should always display payment buttons. - if ( $this->express_checkout_helper->is_pay_for_order_page() ) { - return true; - } - - // Cart total is 0 or is on product page and product price is 0. - // Exclude pay-for-order pages from this check. - if ( - ( ! $this->express_checkout_helper->is_product() && ! $this->express_checkout_helper->is_pay_for_order_page() && 0.0 === (float) WC()->cart->get_total( 'edit' ) ) || - ( $this->express_checkout_helper->is_product() && 0.0 === (float) $this->express_checkout_helper->get_product()->get_price() ) - - ) { - Logger::log( 'Order price is 0 ( Payment Request button disabled )' ); - - return false; - } - - return true; - } - - /** - * Checks to make sure product type is supported. - * - * @return array - */ - public function supported_product_types() { - return apply_filters( - 'wcpay_payment_request_supported_types', - [ - 'simple', - 'variable', - 'variation', - 'subscription', - 'variable-subscription', - 'subscription_variation', - 'booking', - 'bundle', - 'composite', - 'mix-and-match', - ] - ); - } - - /** - * Checks the cart to see if all items are allowed to be used. - * - * @return boolean - */ - public function has_allowed_items_in_cart() { - // Pre Orders compatbility where we don't support charge upon release. - if ( class_exists( 'WC_Pre_Orders_Cart' ) && WC_Pre_Orders_Cart::cart_contains_pre_order() && class_exists( 'WC_Pre_Orders_Product' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( WC_Pre_Orders_Cart::get_pre_order_product() ) ) { - return false; - } - - foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { - $_product = apply_filters( 'woocommerce_cart_item_product', $cart_item['data'], $cart_item, $cart_item_key ); - - if ( ! in_array( $_product->get_type(), $this->supported_product_types(), true ) ) { - return false; - } - - /** - * Filter whether product supports Payment Request Button on cart page. - * - * @param boolean $is_supported Whether product supports Payment Request Button on cart page. - * @param object $_product Product object. - * - * @since 6.9.0 - */ - if ( ! apply_filters( 'wcpay_payment_request_is_cart_supported', true, $_product ) ) { - return false; - } - - // Trial subscriptions with shipping are not supported. - if ( class_exists( 'WC_Subscriptions_Product' ) && WC_Subscriptions_Product::is_subscription( $_product ) && $_product->needs_shipping() && WC_Subscriptions_Product::get_trial_length( $_product ) > 0 ) { - return false; - } - } - - // We don't support multiple packages with Payment Request Buttons because we can't offer a good UX. - $packages = WC()->cart->get_shipping_packages(); - if ( 1 < ( is_countable( $packages ) ? count( $packages ) : 0 ) ) { - return false; - } - - return true; - } - - /** - * Checks whether cart contains a subscription product or this is a subscription product page. - * - * @return boolean - */ - public function has_subscription_product() { - if ( ! class_exists( 'WC_Subscriptions_Product' ) ) { - return false; - } - - if ( $this->express_checkout_helper->is_product() ) { - $product = $this->express_checkout_helper->get_product(); - if ( WC_Subscriptions_Product::is_subscription( $product ) ) { - return true; - } - } - - if ( $this->express_checkout_helper->is_checkout() || $this->express_checkout_helper->is_cart() ) { - if ( WC_Subscriptions_Cart::cart_contains_subscription() ) { - return true; - } - } - - return false; - } - - /** - * Returns the login redirect URL. - * - * @param string $redirect Default redirect URL. - * - * @return string Redirect URL. - */ - public function get_login_redirect_url( $redirect ) { - $url = esc_url_raw( wp_unslash( $_COOKIE['wcpay_payment_request_redirect_url'] ?? '' ) ); - - if ( empty( $url ) ) { - return $redirect; - } - wc_setcookie( 'wcpay_payment_request_redirect_url', '' ); - - return $url; - } - - /** - * Load public scripts and styles. - */ - public function scripts() { - // Don't load scripts if page is not supported. - if ( ! $this->should_show_payment_request_button() ) { - return; - } - - $payment_request_params = [ - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'stripe' => [ - 'publishableKey' => $this->account->get_publishable_key( WC_Payments::mode()->is_test() ), - 'accountId' => $this->account->get_stripe_account_id(), - 'locale' => WC_Payments_Utils::convert_to_stripe_locale( get_locale() ), - ], - 'nonce' => [ - 'get_cart_details' => wp_create_nonce( 'wcpay-get-cart-details' ), - 'shipping' => wp_create_nonce( 'wcpay-payment-request-shipping' ), - 'update_shipping' => wp_create_nonce( 'wcpay-update-shipping-method' ), - 'checkout' => wp_create_nonce( 'woocommerce-process_checkout' ), - 'add_to_cart' => wp_create_nonce( 'wcpay-add-to-cart' ), - 'empty_cart' => wp_create_nonce( 'wcpay-empty-cart' ), - 'get_selected_product_data' => wp_create_nonce( 'wcpay-get-selected-product-data' ), - 'platform_tracker' => wp_create_nonce( 'platform_tracks_nonce' ), - 'pay_for_order' => wp_create_nonce( 'pay_for_order' ), - 'tokenized_cart_nonce' => wp_create_nonce( 'woopayments_tokenized_cart_nonce' ), - 'tokenized_cart_session_nonce' => wp_create_nonce( 'woopayments_tokenized_cart_session_nonce' ), - 'store_api_nonce' => wp_create_nonce( 'wc_store_api' ), - ], - 'checkout' => [ - 'currency_code' => strtolower( get_woocommerce_currency() ), - 'currency_decimals' => WC_Payments::get_localization_service()->get_currency_format( get_woocommerce_currency() )['num_decimals'], - 'country_code' => substr( get_option( 'woocommerce_default_country' ), 0, 2 ), - 'needs_shipping' => WC()->cart->needs_shipping(), - // Defaults to 'required' to match how core initializes this option. - 'needs_payer_phone' => 'required' === get_option( 'woocommerce_checkout_phone_field', 'required' ), - ], - 'button' => $this->get_button_settings(), - 'login_confirmation' => $this->get_login_confirmation_settings(), - 'has_block' => has_block( 'woocommerce/cart' ) || has_block( 'woocommerce/checkout' ), - 'product' => $this->get_product_data(), - 'total_label' => $this->express_checkout_helper->get_total_label(), - 'button_context' => $this->express_checkout_helper->get_button_context(), - 'is_product_page' => $this->express_checkout_helper->is_product(), - 'is_pay_for_order' => $this->express_checkout_helper->is_pay_for_order_page(), - 'is_checkout_page' => $this->express_checkout_helper->is_checkout(), - ]; - - if ( WC_Payments_Features::is_tokenized_cart_ece_enabled() ) { - WC_Payments::register_script_with_dependencies( - 'WCPAY_PAYMENT_REQUEST', - 'dist/tokenized-payment-request', - [ - 'jquery', - 'stripe', - ] - ); - WC_Payments_Utils::enqueue_style( - 'WCPAY_PAYMENT_REQUEST', - plugins_url( 'dist/tokenized-payment-request.css', WCPAY_PLUGIN_FILE ), - [], - WC_Payments::get_file_version( 'dist/tokenized-payment-request.css' ) - ); - } - - wp_localize_script( 'WCPAY_PAYMENT_REQUEST', 'wcpayPaymentRequestParams', $payment_request_params ); - - wp_set_script_translations( 'WCPAY_PAYMENT_REQUEST', 'woocommerce-payments' ); - - wp_enqueue_script( 'WCPAY_PAYMENT_REQUEST' ); - - Fraud_Prevention_Service::maybe_append_fraud_prevention_token(); - - $gateways = WC()->payment_gateways->get_available_payment_gateways(); - if ( isset( $gateways['woocommerce_payments'] ) ) { - WC_Payments::get_wc_payments_checkout()->register_scripts(); - } - } - - /** - * Display the payment request button. - */ - public function display_payment_request_button_html() { - if ( ! $this->should_show_payment_request_button() ) { - return; - } - ?> -
- -
- express_checkout_helper->get_product(); - if ( is_null( $product ) ) { - return false; - } - - if ( ! is_object( $product ) ) { - return false; - } - - if ( ! in_array( $product->get_type(), $this->supported_product_types(), true ) ) { - return false; - } - - // Trial subscriptions with shipping are not supported. - if ( class_exists( 'WC_Subscriptions_Product' ) && $product->needs_shipping() && WC_Subscriptions_Product::get_trial_length( $product ) > 0 ) { - return false; - } - - // Pre Orders charge upon release not supported. - if ( class_exists( 'WC_Pre_Orders_Product' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) { - return false; - } - - // Composite products are not supported on the product page. - if ( class_exists( 'WC_Composite_Products' ) && $product->is_type( 'composite' ) ) { - return false; - } - - // Mix and match products are not supported on the product page. - if ( class_exists( 'WC_Mix_and_Match' ) && $product->is_type( 'mix-and-match' ) ) { - return false; - } - - if ( class_exists( 'WC_Product_Addons_Helper' ) ) { - // File upload addon not supported. - $product_addons = WC_Product_Addons_Helper::get_product_addons( $product->get_id() ); - foreach ( $product_addons as $addon ) { - if ( 'file_upload' === $addon['type'] ) { - return false; - } - } - } - - return true; - } - - /** - * Determine wether to filter the cart needs shipping address. - * - * @param boolean $needs_shipping_address Whether the cart needs a shipping address. - */ - public function filter_cart_needs_shipping_address( $needs_shipping_address ) { - if ( $this->has_subscription_product() && wc_get_shipping_method_count( true, true ) === 0 ) { - return false; - } - - return $needs_shipping_address; - } - - /** - * Calculates whether Apple Pay is enabled for this store. - * The option value is not stored in the database, and is calculated - * using this function instead, and the values is returned by using the pre_option filter. - * - * The option value is retrieved for inbox notifications. - * - * @param mixed $value The value of the option. - */ - public function get_option_is_apple_pay_enabled( $value ) { - // Return a random value (1 or 2) if the account is live and payment request buttons are enabled. - if ( - $this->gateway->is_enabled() - && 'yes' === $this->gateway->get_option( 'payment_request' ) - && ! WC_Payments::mode()->is_dev() - && $this->account->get_is_live() - ) { - $value = wp_rand( 1, 2 ); - } - - return $value; - } - - /** - * Settings array for the user authentication dialog and redirection. - * - * @return array|false - */ - public function get_login_confirmation_settings() { - if ( is_user_logged_in() || ! $this->is_authentication_required() ) { - return false; - } - - /* translators: The text encapsulated in `**` can be replaced with "Apple Pay" or "Google Pay". Please translate this text, but don't remove the `**`. */ - $message = __( 'To complete your transaction with **the selected payment method**, you must log in or create an account with our site.', 'woocommerce-payments' ); - $redirect_url = add_query_arg( - [ - '_wpnonce' => wp_create_nonce( 'wcpay-set-redirect-url' ), - 'wcpay_payment_request_redirect_url' => rawurlencode( home_url( add_query_arg( [] ) ) ), - // Current URL to redirect to after login. - ], - home_url() - ); - - return [ // nosemgrep: audit.php.wp.security.xss.query-arg -- home_url passed in to add_query_arg. - 'message' => $message, - 'redirect_url' => $redirect_url, - ]; - } - - /** - * Calculates taxes as displayed on cart, based on a product and a particular price. - * - * @param WC_Product $product The product, for retrieval of tax classes. - * @param float $price The price, which to calculate taxes for. - * - * @return array An array of final taxes. - */ - private function get_taxes_like_cart( $product, $price ) { - if ( ! wc_tax_enabled() || $this->express_checkout_helper->cart_prices_include_tax() ) { - // Only proceed when taxes are enabled, but not included. - return []; - } - - // Follows the way `WC_Cart_Totals::get_item_tax_rates()` works. - $tax_class = $product->get_tax_class(); - $rates = WC_Tax::get_rates( $tax_class ); - // No cart item, `woocommerce_cart_totals_get_item_tax_rates` can't be applied here. - - // Normally there should be a single tax, but `calc_tax` returns an array, let's use it. - return WC_Tax::calc_tax( $price, $rates, false ); - } -} diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 2a5164e8af3..d0692a8a2b8 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -24,13 +24,6 @@ WC_Pre_Orders_Product - - - WC_Pre_Orders_Product - WC_Subscriptions_Product - WC_Subscriptions_Cart - - WC_Subscriptions_Cart diff --git a/tests/js/jest.config.js b/tests/js/jest.config.js index b81f434b8c5..7a918a8d63c 100644 --- a/tests/js/jest.config.js +++ b/tests/js/jest.config.js @@ -45,8 +45,6 @@ module.exports = { '/.*/build-module/', '/docker/', '/tests/e2e', - // We'll delete the directory and its contents as part of https://github.com/Automattic/woocommerce-payments/issues/9722 . - '/client/tokenized-payment-request', ], transform: { ...tsjPreset.transform, diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php index 99f99b071c2..89ef79bfb11 100755 --- a/tests/unit/bootstrap.php +++ b/tests/unit/bootstrap.php @@ -96,8 +96,6 @@ function () { require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-customer-controller.php'; require_once $_plugin_dir . 'includes/admin/class-wc-rest-payments-refunds-controller.php'; - require_once $_plugin_dir . 'includes/class-wc-payments-payment-request-button-handler.php'; - // Load currency helper class early to ensure its implementation is used over the one resolved during further test initialization. require_once __DIR__ . '/helpers/class-wc-helper-site-currency.php'; diff --git a/tests/unit/test-class-wc-payments-payment-request-button-handler.php b/tests/unit/test-class-wc-payments-payment-request-button-handler.php deleted file mode 100644 index b34299b76f6..00000000000 --- a/tests/unit/test-class-wc-payments-payment-request-button-handler.php +++ /dev/null @@ -1,650 +0,0 @@ - Country_Code::UNITED_STATES, - 'state' => 'CA', - 'postcode' => '94110', - 'city' => 'San Francisco', - 'address_1' => '60 29th Street', - 'address_2' => '#343', - ]; - - /** - * Mock WC_Payments_API_Client. - * - * @var WC_Payments_API_Client - */ - private $mock_api_client; - - /** - * Payment request instance. - * - * @var WC_Payments_Payment_Request_Button_Handler - */ - private $pr; - - /** - * WC_Payments_Account instance. - * - * @var WC_Payments_Account - */ - private $mock_wcpay_account; - - /** - * Test product to add to the cart - * @var WC_Product_Simple - */ - private $simple_product; - - /** - * Test shipping zone. - * - * @var WC_Shipping_Zone - */ - private $zone; - - /** - * Flat rate shipping method instance id - * - * @var int - */ - private $flat_rate_id; - - /** - * Flat rate shipping method instance id - * - * @var int - */ - private $local_pickup_id; - - /** - * Used to get the settings. - * - * @var WC_Payment_Gateway_WCPay - */ - private $mock_wcpay_gateway; - - /** - * Express Checkout Helper instance. - * - * @var WC_Payments_Express_Checkout_Button_Helper - */ - private $express_checkout_helper; - - /** - * Sets up things all tests need. - */ - public function set_up() { - parent::set_up(); - add_filter( 'pre_option_woocommerce_tax_based_on', [ $this, '__return_base' ] ); - - $this->mock_api_client = $this->getMockBuilder( 'WC_Payments_API_Client' ) - ->disableOriginalConstructor() - ->setMethods( - [ - 'get_account_data', - 'is_server_connected', - 'capture_intention', - 'cancel_intention', - 'get_payment_method', - ] - ) - ->getMock(); - $this->mock_api_client->expects( $this->any() )->method( 'is_server_connected' )->willReturn( true ); - $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); - - $this->mock_wcpay_gateway = $this->make_wcpay_gateway(); - - $this->express_checkout_helper = $this->getMockBuilder( WC_Payments_Express_Checkout_Button_Helper::class ) - ->setMethods( - [ - 'is_product', - 'get_product', - ] - ) - ->setConstructorArgs( [ $this->mock_wcpay_gateway, $this->mock_wcpay_account ] ) - ->getMock(); - - $this->pr = new WC_Payments_Payment_Request_Button_Handler( $this->mock_wcpay_account, $this->mock_wcpay_gateway, $this->express_checkout_helper ); - - $this->simple_product = WC_Helper_Product::create_simple_product(); - - WC_Helper_Shipping::delete_simple_flat_rate(); - $zone = new WC_Shipping_Zone(); - $zone->set_zone_name( 'Worldwide' ); - $zone->set_zone_order( 1 ); - $zone->save(); - - add_filter( - 'woocommerce_find_rates', - function () { - return [ - 1 => - [ - 'rate' => 10.0, - 'label' => 'Tax', - 'shipping' => 'yes', - 'compound' => 'no', - ], - ]; - }, - 50, - 2 - ); - - $this->flat_rate_id = $zone->add_shipping_method( 'flat_rate' ); - self::set_shipping_method_cost( $this->flat_rate_id, '5' ); - - $this->local_pickup_id = $zone->add_shipping_method( 'local_pickup' ); - self::set_shipping_method_cost( $this->local_pickup_id, '1' ); - - $this->zone = $zone; - - WC()->session->init(); - WC()->cart->add_to_cart( $this->simple_product->get_id(), 1 ); - WC()->cart->calculate_totals(); - } - - public function tear_down() { - WC_Subscriptions_Cart::set_cart_contains_subscription( false ); - WC()->cart->empty_cart(); - WC()->session->cleanup_sessions(); - $this->zone->delete(); - delete_option( 'woocommerce_woocommerce_payments_settings' ); - remove_filter( 'pre_option_woocommerce_tax_based_on', [ $this, '__return_base' ] ); - remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_excl' ] ); - remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_incl' ] ); - remove_filter( 'pre_option_woocommerce_tax_display_shop', [ $this, '__return_excl' ] ); - remove_filter( 'pre_option_woocommerce_tax_display_shop', [ $this, '__return_incl' ] ); - remove_filter( 'pre_option_woocommerce_prices_include_tax', [ $this, '__return_yes' ] ); - remove_filter( 'pre_option_woocommerce_prices_include_tax', [ $this, '__return_no' ] ); - remove_filter( 'wc_tax_enabled', '__return_true' ); - remove_filter( 'wc_tax_enabled', '__return_false' ); - remove_filter( 'wc_shipping_enabled', '__return_false' ); - remove_all_filters( 'woocommerce_find_rates' ); - - parent::tear_down(); - } - - public function __return_yes() { - return 'yes'; - } - - public function __return_no() { - return 'no'; - } - - public function __return_excl() { - return 'excl'; - } - - public function __return_incl() { - return 'incl'; - } - - public function __return_base() { - return 'base'; - } - - /** - * @return WC_Payment_Gateway_WCPay - */ - private function make_wcpay_gateway() { - $mock_customer_service = $this->createMock( WC_Payments_Customer_Service::class ); - $mock_token_service = $this->createMock( WC_Payments_Token_Service::class ); - $mock_action_scheduler_service = $this->createMock( WC_Payments_Action_Scheduler_Service::class ); - $mock_rate_limiter = $this->createMock( Session_Rate_Limiter::class ); - $mock_order_service = $this->createMock( WC_Payments_Order_Service::class ); - $mock_dpps = $this->createMock( Duplicate_Payment_Prevention_Service::class ); - $mock_payment_method = $this->createMock( CC_Payment_Method::class ); - - return new WC_Payment_Gateway_WCPay( - $this->mock_api_client, - $this->mock_wcpay_account, - $mock_customer_service, - $mock_token_service, - $mock_action_scheduler_service, - $mock_payment_method, - [ 'card' => $mock_payment_method ], - $mock_order_service, - $mock_dpps, - $this->createMock( WC_Payments_Localization_Service::class ), - $this->createMock( WC_Payments_Fraud_Service::class ), - $this->createMock( Duplicates_Detection_Service::class ), - $mock_rate_limiter - ); - } - - /** - * Sets shipping method cost - * - * @param string $instance_id Shipping method instance id - * @param string $cost Shipping method cost in USD - */ - private static function set_shipping_method_cost( $instance_id, $cost ) { - $method = WC_Shipping_Zones::get_shipping_method( $instance_id ); - $option_key = $method->get_instance_option_key(); - $options = get_option( $option_key ); - $options['cost'] = $cost; - update_option( $option_key, $options ); - } - - /** - * Retrieves rate id by shipping method instance id. - * - * @param string $instance_id Shipping method instance id. - * - * @return string Shipping option instance rate id. - */ - private static function get_shipping_option_rate_id( $instance_id ) { - $method = WC_Shipping_Zones::get_shipping_method( $instance_id ); - - return $method->get_rate_id(); - } - - public function test_multiple_packages_in_cart_not_allowed() { - // Add fake packages to the cart. - add_filter( - 'woocommerce_cart_shipping_packages', - function () { - return [ - 'fake_package_1', - 'fake_package_2', - ]; - } - ); - $this->mock_wcpay_gateway = $this->make_wcpay_gateway(); - $this->pr = new WC_Payments_Payment_Request_Button_Handler( $this->mock_wcpay_account, $this->mock_wcpay_gateway, $this->express_checkout_helper ); - - $this->assertFalse( $this->pr->has_allowed_items_in_cart() ); - } - - public function test_get_product_price_returns_simple_price() { - $this->assertEquals( - $this->simple_product->get_price(), - $this->pr->get_product_price( $this->simple_product ) - ); - } - - public function test_get_product_price_returns_deposit_amount() { - $product_price = 10; - $this->simple_product->set_price( $product_price ); - - $this->assertEquals( - $product_price, - $this->pr->get_product_price( $this->simple_product, false ), - 'When deposit is disabled, the regular price should be returned.' - ); - $this->assertEquals( - $product_price, - $this->pr->get_product_price( $this->simple_product, true ), - 'When deposit is enabled, but the product has no setting for deposit, the regular price should be returned.' - ); - - $this->simple_product->update_meta_data( '_wc_deposit_enabled', 'optional' ); - $this->simple_product->update_meta_data( '_wc_deposit_type', 'percent' ); - $this->simple_product->update_meta_data( '_wc_deposit_amount', 50 ); - $this->simple_product->save_meta_data(); - - $this->assertEquals( - $product_price, - $this->pr->get_product_price( $this->simple_product, false ), - 'When deposit is disabled, the regular price should be returned.' - ); - $this->assertEquals( - $product_price * 0.5, - $this->pr->get_product_price( $this->simple_product, true ), - 'When deposit is enabled, the deposit price should be returned.' - ); - - $this->simple_product->delete_meta_data( '_wc_deposit_amount' ); - $this->simple_product->delete_meta_data( '_wc_deposit_type' ); - $this->simple_product->delete_meta_data( '_wc_deposit_enabled' ); - $this->simple_product->save_meta_data(); - } - - public function test_get_product_price_returns_deposit_amount_default_values() { - $product_price = 10; - $this->simple_product->set_price( $product_price ); - - $this->assertEquals( - $product_price, - $this->pr->get_product_price( $this->simple_product ), - 'When deposit is disabled by default, the regular price should be returned.' - ); - - $this->simple_product->update_meta_data( '_wc_deposit_enabled', 'optional' ); - $this->simple_product->update_meta_data( '_wc_deposit_type', 'percent' ); - $this->simple_product->update_meta_data( '_wc_deposit_amount', 50 ); - $this->simple_product->update_meta_data( '_wc_deposit_selected_type', 'full' ); - $this->simple_product->save_meta_data(); - - $this->assertEquals( - $product_price, - $this->pr->get_product_price( $this->simple_product ), - 'When deposit is optional and disabled by default, the regular price should be returned.' - ); - - $this->simple_product->update_meta_data( '_wc_deposit_selected_type', 'deposit' ); - $this->simple_product->save_meta_data(); - - $this->assertEquals( - $product_price * 0.5, - $this->pr->get_product_price( $this->simple_product ), - 'When deposit is optional and selected by default, the deposit price should be returned.' - ); - } - - /** - * @dataProvider provide_get_product_tax_tests - */ - public function test_get_product_price_returns_price_with_tax( $tax_enabled, $prices_include_tax, $tax_display_shop, $tax_display_cart, $product_price, $expected_price ) { - $this->simple_product->set_price( $product_price ); - add_filter( 'wc_tax_enabled', $tax_enabled ? '__return_true' : '__return_false' ); // reset in tear_down. - add_filter( 'pre_option_woocommerce_prices_include_tax', [ $this, "__return_$prices_include_tax" ] ); // reset in tear_down. - add_filter( 'pre_option_woocommerce_tax_display_shop', [ $this, "__return_$tax_display_shop" ] ); // reset in tear_down. - add_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, "__return_$tax_display_cart" ] ); // reset in tear_down. - WC()->cart->calculate_totals(); - $this->assertEquals( - $expected_price, - $this->pr->get_product_price( $this->simple_product ) - ); - } - - public function provide_get_product_tax_tests() { - yield 'Tax Disabled, Price Display Unaffected' => [ - 'tax_enabled' => false, - 'prices_include_tax' => 'no', - 'tax_display_shop' => 'excl', - 'tax_display_cart' => 'incl', - 'product_price' => 10, - 'expected_price' => 10, - ]; - - // base prices DO NOT include tax. - - yield 'Shop: Excl / Cart: Incl, Base Prices Don\'t Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'no', - 'tax_display_shop' => 'excl', - 'tax_display_cart' => 'incl', - 'product_price' => 10, - 'expected_price' => 11, - ]; - yield 'Shop: Excl / Cart: Excl, Base Prices Don\'t Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'no', - 'tax_display_shop' => 'excl', - 'tax_display_cart' => 'excl', - 'product_price' => 10, - 'expected_price' => 10, - ]; - - yield 'Shop: Incl / Cart: Incl, Base Prices Don\'t Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'no', - 'tax_display_shop' => 'incl', - 'tax_display_cart' => 'incl', - 'product_price' => 10, - 'expected_price' => 11, - ]; - yield 'Shop: Incl / Cart: Excl, Base Prices Don\'t Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'no', - 'tax_display_shop' => 'incl', - 'tax_display_cart' => 'excl', - 'product_price' => 10, - 'expected_price' => 10, - ]; - - // base prices include tax. - - yield 'Shop: Excl / Cart: Incl, Base Prices Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'yes', - 'tax_display_shop' => 'excl', - 'tax_display_cart' => 'incl', - 'product_price' => 10, - 'expected_price' => 10, - ]; - yield 'Shop: Excl / Cart: Excl, Base Prices Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'yes', - 'tax_display_shop' => 'excl', - 'tax_display_cart' => 'excl', - 'product_price' => 10, - 'expected_price' => 9.090909, - ]; - - yield 'Shop: Incl / Cart: Incl, Base Prices Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'yes', - 'tax_display_shop' => 'incl', - 'tax_display_cart' => 'incl', - 'product_price' => 10, - 'expected_price' => 10, - ]; - yield 'Shop: Incl / Cart: Excl, Base Prices Include Tax' => [ - 'tax_enabled' => true, - 'prices_include_tax' => 'yes', - 'tax_display_shop' => 'incl', - 'tax_display_cart' => 'excl', - 'product_price' => 10, - 'expected_price' => 9.090909, - ]; - } - - public function test_get_product_price_includes_subscription_sign_up_fee() { - $mock_product = $this->create_mock_subscription( 'subscription' ); - add_filter( - 'test_deposit_get_product', - function () use ( $mock_product ) { - return $mock_product; - } - ); - - // We have a helper because we are not loading subscriptions. - WC_Subscriptions_Product::set_sign_up_fee( 10 ); - - $this->assertEquals( 20, $this->pr->get_product_price( $mock_product ) ); - - // Restore the sign-up fee after the test. - WC_Subscriptions_Product::set_sign_up_fee( 0 ); - - remove_all_filters( 'test_deposit_get_product' ); - } - - public function test_get_product_price_includes_variable_subscription_sign_up_fee() { - $mock_product = $this->create_mock_subscription( 'subscription_variation' ); - add_filter( - 'test_deposit_get_product', - function () use ( $mock_product ) { - return $mock_product; - } - ); - - // We have a helper because we are not loading subscriptions. - WC_Subscriptions_Product::set_sign_up_fee( 10 ); - - $this->assertEquals( 20, $this->pr->get_product_price( $mock_product ) ); - - // Restore the sign-up fee after the test. - WC_Subscriptions_Product::set_sign_up_fee( 0 ); - - remove_all_filters( 'test_deposit_get_product' ); - } - - public function test_get_product_price_throws_exception_for_products_without_prices() { - if ( version_compare( WC_VERSION, '6.9.0', '>=' ) ) { - $this->markTestSkipped( 'This test is useless starting with WooCommerce 6.9.0' ); - return; - } - - $this->simple_product->set_price( 'a' ); - - $this->expectException( WCPay\Exceptions\Invalid_Price_Exception::class ); - - $this->pr->get_product_price( $this->simple_product ); - } - - public function test_get_product_price_throws_exception_for_a_non_numeric_signup_fee() { - $mock_product = $this->create_mock_subscription( 'subscription' ); - add_filter( - 'test_deposit_get_product', - function () use ( $mock_product ) { - return $mock_product; - } - ); - WC_Subscriptions_Product::set_sign_up_fee( 'a' ); - - $this->expectException( WCPay\Exceptions\Invalid_Price_Exception::class ); - $this->pr->get_product_price( $mock_product ); - - // Restore the sign-up fee after the test. - WC_Subscriptions_Product::set_sign_up_fee( 0 ); - - remove_all_filters( 'test_deposit_get_product' ); - } - - private function create_mock_subscription( $type ) { - $mock_product = $this->createMock( WC_Subscriptions_Product::class ); - - $mock_product - ->expects( $this->once() ) - ->method( 'get_price' ) - ->willReturn( 10 ); - - $mock_product - ->expects( $this->once() ) - ->method( 'get_type' ) - ->willReturn( $type ); - - return $mock_product; - } - - /** - * @dataProvider provide_get_product_tax_tests - */ - public function test_get_product_data_returns_the_same_as_build_display_items_without_shipping( $tax_enabled, $prices_include_tax, $tax_display_shop, $tax_display_cart, $_product_price, $_expected_price ) { - add_filter( 'wc_tax_enabled', $tax_enabled ? '__return_true' : '__return_false' ); // reset in tear_down. - add_filter( 'pre_option_woocommerce_prices_include_tax', [ $this, "__return_$prices_include_tax" ] ); // reset in tear_down. - add_filter( 'pre_option_woocommerce_tax_display_shop', [ $this, "__return_$tax_display_shop" ] ); // reset in tear_down. - add_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, "__return_$tax_display_cart" ] ); // reset in tear_down. - add_filter( 'wc_shipping_enabled', '__return_false' ); // reset in tear_down. - WC()->cart->calculate_totals(); - $build_display_items_result = $this->express_checkout_helper->build_display_items( true ); - - $this->express_checkout_helper - ->method( 'is_product' ) - ->willReturn( true ); - - $this->express_checkout_helper - ->method( 'get_product' ) - ->willReturn( $this->simple_product ); - - $get_product_data_result = $this->pr->get_product_data(); - - foreach ( $get_product_data_result['displayItems'] as $key => $display_item ) { - if ( isset( $display_item['pending'] ) ) { - unset( $get_product_data_result['displayItems'][ $key ]['pending'] ); - } - } - - $this->assertEquals( - $get_product_data_result['displayItems'], - $build_display_items_result['displayItems'], - 'Failed asserting displayItems are the same for get_product_data and build_display_items' - ); - $this->assertEquals( - $get_product_data_result['total']['amount'], - $build_display_items_result['total']['amount'], - 'Failed asserting total amount are the same for get_product_data and build_display_items' - ); - } - - public function test_filter_cart_needs_shipping_address_returns_false() { - sleep( 1 ); - $this->zone->delete_shipping_method( $this->flat_rate_id ); - $this->zone->delete_shipping_method( $this->local_pickup_id ); - - WC_Subscriptions_Cart::set_cart_contains_subscription( true ); - - $this->assertFalse( $this->pr->filter_cart_needs_shipping_address( true ) ); - } - - public function test_filter_cart_needs_shipping_address_returns_true() { - WC_Subscriptions_Cart::set_cart_contains_subscription( true ); - - $this->assertTrue( $this->pr->filter_cart_needs_shipping_address( true ) ); - } - - public function test_get_button_settings() { - $this->express_checkout_helper - ->method( 'is_product' ) - ->willReturn( true ); - - $this->assertEquals( - [ - 'type' => 'default', - 'theme' => 'dark', - 'height' => '48', - 'locale' => 'en', - 'branded_type' => 'short', - 'radius' => '', - ], - $this->pr->get_button_settings() - ); - } - - public function test_filter_gateway_title() { - $order = $this->createMock( WC_Order::class ); - $order->method( 'get_payment_method_title' )->willReturn( 'Apple Pay' ); - - global $theorder; - $theorder = $order; - - $this->set_is_admin( true ); - $this->assertEquals( 'Apple Pay', $this->pr->filter_gateway_title( 'Original Title', 'woocommerce_payments' ) ); - - $this->set_is_admin( false ); - $this->assertEquals( 'Original Title', $this->pr->filter_gateway_title( 'Original Title', 'woocommerce_payments' ) ); - - $this->set_is_admin( true ); - $this->assertEquals( 'Original Title', $this->pr->filter_gateway_title( 'Original Title', 'another_gateway' ) ); - } - - /** - * @param bool $is_admin - */ - private function set_is_admin( bool $is_admin ) { - global $current_screen; - - if ( ! $is_admin ) { - $current_screen = null; // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited - return; - } - - // phpcs:ignore: WordPress.WP.GlobalVariablesOverride.Prohibited - $current_screen = $this->getMockBuilder( \stdClass::class ) - ->setMethods( [ 'in_admin' ] ) - ->getMock(); - - $current_screen->method( 'in_admin' )->willReturn( $is_admin ); - } -} From 1f857c7e2212110f317502e48c91af8d0c730352 Mon Sep 17 00:00:00 2001 From: Daniel Guerra <15204776+danielmx-dev@users.noreply.github.com> Date: Thu, 12 Dec 2024 12:12:36 -0600 Subject: [PATCH 37/83] Skip mysqlcheck SSL Requirement during E2E environment setup (#9941) --- bin/docker-setup.sh | 4 ++-- changelog/fix-skip-ssl-requirement-env-setup | 4 ++++ tests/e2e/env/setup.sh | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 changelog/fix-skip-ssl-requirement-env-setup diff --git a/bin/docker-setup.sh b/bin/docker-setup.sh index 66c9fa1ee8b..6db41510908 100755 --- a/bin/docker-setup.sh +++ b/bin/docker-setup.sh @@ -27,11 +27,11 @@ cli() set +e # Wait for containers to be started up before the setup. # The db being accessible means that the db container started and the WP has been downloaded and the plugin linked -cli wp db check --path=/var/www/html --quiet > /dev/null +cli wp db check --skip_ssl --path=/var/www/html --quiet > /dev/null while [[ $? -ne 0 ]]; do echo "Waiting until the service is ready..." sleep 5 - cli wp db check --path=/var/www/html --quiet > /dev/null + cli wp db check --skip_ssl --path=/var/www/html --quiet > /dev/null done # If the plugin is already active then return early diff --git a/changelog/fix-skip-ssl-requirement-env-setup b/changelog/fix-skip-ssl-requirement-env-setup new file mode 100644 index 00000000000..691f98adbfa --- /dev/null +++ b/changelog/fix-skip-ssl-requirement-env-setup @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Skip mysqlcheck SSL Requirement during E2E environment setup diff --git a/tests/e2e/env/setup.sh b/tests/e2e/env/setup.sh index d2aa3a50e89..5ab08183bac 100755 --- a/tests/e2e/env/setup.sh +++ b/tests/e2e/env/setup.sh @@ -123,11 +123,11 @@ step "Setting up CLIENT site" # Wait for containers to be started up before the setup. # The db being accessible means that the db container started and the WP has been downloaded and the plugin linked set +e -cli wp db check --path=/var/www/html --quiet > /dev/null +cli wp db check --skip_ssl --path=/var/www/html --quiet > /dev/null while [[ $? -ne 0 ]]; do echo "Waiting until the service is ready..." sleep 5 - cli wp db check --path=/var/www/html --quiet > /dev/null + cli wp db check --skip_ssl --path=/var/www/html --quiet > /dev/null done echo "Client DB is up and running..." set -e From aa3203c358bb19c072fb48551394176fa6c2ef4e Mon Sep 17 00:00:00 2001 From: Nagesh Pai <4162931+nagpai@users.noreply.github.com> Date: Fri, 13 Dec 2024 11:02:51 +0530 Subject: [PATCH 38/83] Reports: Update Transaction ID column label (#9911) Co-authored-by: Nagesh Pai Co-authored-by: Rua Haszard --- changelog/update-9910-transaction-id-label | 5 +++++ client/transactions/list/index.tsx | 2 +- client/transactions/list/test/index.tsx | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelog/update-9910-transaction-id-label diff --git a/changelog/update-9910-transaction-id-label b/changelog/update-9910-transaction-id-label new file mode 100644 index 00000000000..0e43652d02b --- /dev/null +++ b/changelog/update-9910-transaction-id-label @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Change ID to uppercase in the 'Transaction ID' column label for consistency with similar unique IDs in the UI. + + diff --git a/client/transactions/list/index.tsx b/client/transactions/list/index.tsx index a8c437d6d2c..a5206ae0e5e 100644 --- a/client/transactions/list/index.tsx +++ b/client/transactions/list/index.tsx @@ -151,7 +151,7 @@ const getColumns = ( [ { key: 'transaction_id', - label: __( 'Transaction Id', 'woocommerce-payments' ), + label: __( 'Transaction ID', 'woocommerce-payments' ), visible: false, isLeftAligned: true, }, diff --git a/client/transactions/list/test/index.tsx b/client/transactions/list/test/index.tsx index b2cf7f56664..b233b4d5477 100644 --- a/client/transactions/list/test/index.tsx +++ b/client/transactions/list/test/index.tsx @@ -621,7 +621,7 @@ describe( 'Transactions list', () => { getByRole( 'button', { name: 'Download' } ).click(); const expected = [ - '"Transaction Id"', + '"Transaction ID"', '"Date / Time (UTC)"', 'Type', 'Channel', From 6a67203525f66a592f284d5e05849b455f99799d Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Fri, 13 Dec 2024 13:44:44 +0100 Subject: [PATCH 39/83] Fix issue where order status and data are not updated when an order is in the processing state and being captured. (#9922) --- ...ndle-error-on-refund-during-manual-capture | 4 +++ includes/class-wc-payment-gateway-wcpay.php | 2 +- includes/class-wc-payments-order-service.php | 15 ++++++++++ .../test-class-wc-payments-order-service.php | 29 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-5671-handle-error-on-refund-during-manual-capture diff --git a/changelog/fix-5671-handle-error-on-refund-during-manual-capture b/changelog/fix-5671-handle-error-on-refund-during-manual-capture new file mode 100644 index 00000000000..016c68f13aa --- /dev/null +++ b/changelog/fix-5671-handle-error-on-refund-during-manual-capture @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed an issue where order metadata was not updated when capturing an order in the processing state. diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 0f53c3dde42..cff7022a1b9 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -3373,7 +3373,7 @@ public function capture_charge( $order, $include_level3 = true, $intent_metadata $this->attach_exchange_info_to_order( $order, $charge_id ); if ( Intent_Status::SUCCEEDED === $status ) { - $this->order_service->update_order_status_from_intent( $order, $intent ); + $this->order_service->process_captured_payment( $order, $intent ); } elseif ( $is_authorization_expired ) { $this->order_service->mark_payment_capture_expired( $order, $intent_id, Intent_Status::CANCELED, $charge_id ); } else { diff --git a/includes/class-wc-payments-order-service.php b/includes/class-wc-payments-order-service.php index c563877e830..f685b50debf 100644 --- a/includes/class-wc-payments-order-service.php +++ b/includes/class-wc-payments-order-service.php @@ -190,6 +190,21 @@ public function update_order_status_from_intent( $order, $intent ) { $this->complete_order_processing( $order ); } + /** + * Handles the order state when a payment is captured successfully. + * Unlike `update_order_status_from_intent`, this method does not check the current order status or skip processing + * if the order is already in the "processing" state. This ensures the order status is updated correctly upon a + * successful capture, preventing issues where the capture is not reflected in the order details or transaction screens + * due to the order status being in the processing state. + * + * @param WC_Order $order The order to update. + * @param WC_Payments_API_Abstract_Intention $intent The intent object containing payment or setup data. + */ + public function process_captured_payment( $order, $intent ) { + $this->mark_payment_capture_completed( $order, $intent ); + $this->complete_order_processing( $order, $intent->get_status() ); + } + /** * Updates an order to failed status, while adding a note with a link to the transaction. * diff --git a/tests/unit/test-class-wc-payments-order-service.php b/tests/unit/test-class-wc-payments-order-service.php index 2eeaa50864e..d3fef27ea1f 100644 --- a/tests/unit/test-class-wc-payments-order-service.php +++ b/tests/unit/test-class-wc-payments-order-service.php @@ -1382,4 +1382,33 @@ public function test_add_note_and_metadata_for_refund_partially_refunded(): void WC_Helper_Order::delete_order( $order->get_id() ); } + + public function test_process_captured_payment() { + $order = WC_Helper_Order::create_order(); + $order->save(); + + $intent = WC_Helper_Intention::create_intention( [ 'status' => Intent_Status::SUCCEEDED ] ); + $this->order_service->set_intention_status_for_order( $this->order, Intent_Status::REQUIRES_CAPTURE ); + $this->order_service->set_intent_id_for_order( $order, $intent->get_id() ); + $order->set_status( Order_Status::PROCESSING ); // Let's simulate that order is set to processing, so order status should not interfere with the process. + $order->save(); + + $this->order_service->process_captured_payment( $order, $intent ); + + $this->assertEquals( $intent->get_status(), $this->order_service->get_intention_status_for_order( $order ) ); + + $this->assertTrue( $order->has_status( wc_get_is_paid_statuses() ) ); + + $notes = wc_get_order_notes( [ 'order_id' => $order->get_id() ] ); + $this->assertStringContainsString( 'successfully captured
using WooPayments', $notes[0]->content ); + $this->assertStringContainsString( '/payments/transactions/details&id=pi_mock" target="_blank" rel="noopener noreferrer">pi_mock', $notes[0]->content ); + + // Assert: Check that the order was unlocked. + $this->assertFalse( get_transient( 'wcpay_processing_intent_' . $order->get_id() ) ); + + // Assert: Applying the same data multiple times does not cause duplicate actions. + $this->order_service->update_order_status_from_intent( $order, $intent ); + $notes_2 = wc_get_order_notes( [ 'order_id' => $order->get_id() ] ); + $this->assertEquals( count( $notes ), count( $notes_2 ) ); + } } From 42bf30bc949206b42147e3565253ab8d8514239b Mon Sep 17 00:00:00 2001 From: Dan Paun <82826872+dpaun1985@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:17:16 +0200 Subject: [PATCH 40/83] Migrate capabilities from test-drive to live account (#9928) Co-authored-by: Dan Paun --- .../add-6924-migrate-test-drive-capabilities | 4 ++ includes/class-wc-payments-account.php | 47 +++++++++++++++++-- .../class-wc-payments-onboarding-service.php | 24 ++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 changelog/add-6924-migrate-test-drive-capabilities diff --git a/changelog/add-6924-migrate-test-drive-capabilities b/changelog/add-6924-migrate-test-drive-capabilities new file mode 100644 index 00000000000..7b280af4d92 --- /dev/null +++ b/changelog/add-6924-migrate-test-drive-capabilities @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Migrate active capabilities from test-drive account when switching to live account. diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 85b0f336263..611c3a8acd2 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -30,6 +30,7 @@ class WC_Payments_Account implements MultiCurrencyAccountInterface { const ONBOARDING_STARTED_TRANSIENT = 'wcpay_on_boarding_started'; const ONBOARDING_STATE_TRANSIENT = 'wcpay_stripe_onboarding_state'; const WOOPAY_ENABLED_BY_DEFAULT_TRANSIENT = 'woopay_enabled_by_default'; + const ONBOARDING_TEST_DRIVE_SETTINGS_FOR_LIVE_ACCOUNT = 'test_drive_account_settings_for_live_account'; const EMBEDDED_KYC_IN_PROGRESS_OPTION = 'wcpay_onboarding_embedded_kyc_in_progress'; const ERROR_MESSAGE_TRANSIENT = 'wcpay_error_message'; const INSTANT_DEPOSITS_REMINDER_ACTION = 'wcpay_instant_deposit_reminder'; @@ -1317,6 +1318,7 @@ public function maybe_handle_onboarding() { } $this->cleanup_on_account_reset(); + delete_transient( self::ONBOARDING_TEST_DRIVE_SETTINGS_FOR_LIVE_ACCOUNT ); // When we reset the account and want to go back to the settings page - redirect immediately! if ( $redirect_to_settings_page ) { @@ -1342,6 +1344,10 @@ public function maybe_handle_onboarding() { // in the "everything OK" scenario). if ( WC_Payments_Onboarding_Service::is_test_mode_enabled() ) { try { + // If we're in test mode and dealing with a test-drive account, + // we need to collect the test drive settings before we delete the test-drive account, + // and apply those settings to the live account. + $this->save_test_drive_settings(); // Delete the currently connected Stripe account. $this->payments_api_client->delete_account( true ); } catch ( API_Exception $e ) { @@ -1426,7 +1432,6 @@ public function maybe_handle_onboarding() { if ( ! $collect_payout_requirements && $this->has_working_jetpack_connection() && $this->is_stripe_account_valid() ) { - $params = [ 'source' => $onboarding_source, // Carry over some parameters as they may be used by our frontend logic. @@ -2149,13 +2154,11 @@ private function finalize_connection( string $state, string $mode, array $additi // If we get this parameter, but we have a valid state, it means the merchant left KYC early and didn't finish it. // While we do have an account, it is not yet valid. We need to redirect them back to the connect page. $params['wcpay-connection-error'] = '1'; - $this->redirect_service->redirect_to_connect_page( '', WC_Payments_Onboarding_Service::FROM_STRIPE, $params ); return; } $params['wcpay-connection-success'] = '1'; - $this->redirect_service->redirect_to_overview_page( WC_Payments_Onboarding_Service::FROM_STRIPE, $params ); } @@ -2582,4 +2585,42 @@ public function get_lifetime_total_payment_volume(): int { $account = $this->get_cached_account_data(); return (int) ! empty( $account ) && isset( $account['lifetime_total_payment_volume'] ) ? $account['lifetime_total_payment_volume'] : 0; } + + /** + * Extract the test drive settings from the account data that we want to store for the live account. + * ATM we only store the enabled payment methods. + * + * @return array The test drive settings for the live account. + */ + private function get_test_drive_settings_for_live_account(): array { + $gateway = WC_Payments::get_gateway(); + + $capabilities = []; + foreach ( $gateway->get_upe_enabled_payment_method_ids() as $payment_method_id ) { + $capabilities[ $payment_method_id . '_payments' ] = [ 'requested' => 'true' ]; + } + + return [ 'capabilities' => $capabilities ]; + } + + /** + * If we're in test mode and dealing with a test-drive account, + * we need to collect the test drive settings before we delete the test-drive account, + * and apply those settings to the live account. + * + * @return void + */ + private function save_test_drive_settings(): void { + $account = $this->get_cached_account_data(); + + if ( ! empty( $account['is_test_drive'] ) && true === $account['is_test_drive'] ) { + $test_drive_account_data = $this->get_test_drive_settings_for_live_account(); + + // Store the test drive settings for the live account in a transient, + // We don't passing the data around, as the merchant might cancel and start + // the onboarding from scratch. In this case, we won't have the test drive + // account anymore to collect the settings. + set_transient( self::ONBOARDING_TEST_DRIVE_SETTINGS_FOR_LIVE_ACCOUNT, $test_drive_account_data, HOUR_IN_SECONDS ); + } + } } diff --git a/includes/class-wc-payments-onboarding-service.php b/includes/class-wc-payments-onboarding-service.php index bce87bcbf0c..ce558ac2faf 100644 --- a/includes/class-wc-payments-onboarding-service.php +++ b/includes/class-wc-payments-onboarding-service.php @@ -109,6 +109,7 @@ public function __construct( WC_Payments_API_Client $payments_api_client, Databa */ public function init_hooks() { add_filter( 'admin_body_class', [ $this, 'add_admin_body_classes' ] ); + add_filter( 'wc_payments_get_onboarding_data_args', [ $this, 'maybe_add_test_drive_settings_to_new_account_request' ] ); } /** @@ -902,4 +903,27 @@ public static function get_source( ?string $referer = null, ?array $get_params = // Default to an unknown source. return self::SOURCE_UNKNOWN; } + + /** + * If settings are collected from the test-drive account, + * include them in the existing arguments when creating the new account. + * + * @param array $args The request args to create new account. + * + * @return array The request args, possible updated with the test drive account settings, used to create new account. + */ + public function maybe_add_test_drive_settings_to_new_account_request( array $args ): array { + if ( + get_transient( WC_Payments_Account::ONBOARDING_TEST_DRIVE_SETTINGS_FOR_LIVE_ACCOUNT ) && + is_array( get_transient( WC_Payments_Account::ONBOARDING_TEST_DRIVE_SETTINGS_FOR_LIVE_ACCOUNT ) ) + ) { + $args['account_data'] = array_merge( + $args['account_data'], + get_transient( WC_Payments_Account::ONBOARDING_TEST_DRIVE_SETTINGS_FOR_LIVE_ACCOUNT ) + ); + delete_transient( WC_Payments_Account::ONBOARDING_TEST_DRIVE_SETTINGS_FOR_LIVE_ACCOUNT ); + } + + return $args; + } } From 1db617817604abba9ccb0ae092c81c1890414065 Mon Sep 17 00:00:00 2001 From: Ahmed Date: Fri, 13 Dec 2024 16:13:52 +0100 Subject: [PATCH 41/83] Fix Jetpack onboarding by replacing the "from" query param (#9949) --- changelog/replace-from-url-query | 4 ++++ includes/wc-payment-api/class-wc-payments-http.php | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelog/replace-from-url-query diff --git a/changelog/replace-from-url-query b/changelog/replace-from-url-query new file mode 100644 index 00000000000..58688e1c42f --- /dev/null +++ b/changelog/replace-from-url-query @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" diff --git a/includes/wc-payment-api/class-wc-payments-http.php b/includes/wc-payment-api/class-wc-payments-http.php index 658529190ed..1dc14048cfb 100644 --- a/includes/wc-payment-api/class-wc-payments-http.php +++ b/includes/wc-payment-api/class-wc-payments-http.php @@ -199,7 +199,7 @@ public function start_connection( $redirect ) { wp_safe_redirect( add_query_arg( [ - 'from' => 'woocommerce-payments', + 'from' => 'woocommerce-core-profiler', 'plugin_name' => 'woocommerce-payments', 'calypso_env' => $calypso_env, ], From 2e14de5dae812c3b02cf617fbac1448f71c29ed6 Mon Sep 17 00:00:00 2001 From: Igor Zinovyev Date: Fri, 13 Dec 2024 18:42:03 +0300 Subject: [PATCH 42/83] Added a callback instead of a direct call to avoid i18n notices. (#9884) Co-authored-by: Cvetan Cvetanov --- changelog/add-jetpack-config-callback | 4 + composer.json | 8 +- composer.lock | 379 +++++++++++++------------- woocommerce-payments.php | 8 +- 4 files changed, 206 insertions(+), 193 deletions(-) create mode 100644 changelog/add-jetpack-config-callback diff --git a/changelog/add-jetpack-config-callback b/changelog/add-jetpack-config-callback new file mode 100644 index 00000000000..64b1a2abb1b --- /dev/null +++ b/changelog/add-jetpack-config-callback @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Added conditional use of Jetpack Config callback to avoid i18n notices. diff --git a/composer.json b/composer.json index f5b03aaa04f..13f17e9c000 100644 --- a/composer.json +++ b/composer.json @@ -22,10 +22,10 @@ "require": { "php": ">=7.3", "ext-json": "*", - "automattic/jetpack-connection": "2.12.4", - "automattic/jetpack-config": "2.0.4", - "automattic/jetpack-autoloader": "3.0.10", - "automattic/jetpack-sync": "3.8.0", + "automattic/jetpack-connection": "6.2.0", + "automattic/jetpack-config": "3.0.0", + "automattic/jetpack-autoloader": "5.0.0", + "automattic/jetpack-sync": "4.1.0", "woocommerce/subscriptions-core": "6.7.1" }, "require-dev": { diff --git a/composer.lock b/composer.lock index f6276dc29e7..3e1d4ee08df 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,27 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2f2c365c1ebb8b6af6e0df8c0ba64709", + "content-hash": "ed20d78f8b2b14b67df2266bd7614d62", "packages": [ { "name": "automattic/jetpack-a8c-mc-stats", - "version": "v2.0.4", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-a8c-mc-stats.git", - "reference": "d1d726e4962d4bf6f9c51d01e63d613c3a9dd0bb" + "reference": "d6bdf2f1d1941e0a22d17c6f3152097d8e0a30e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-a8c-mc-stats/zipball/d1d726e4962d4bf6f9c51d01e63d613c3a9dd0bb", - "reference": "d1d726e4962d4bf6f9c51d01e63d613c3a9dd0bb", + "url": "https://api.github.com/repos/Automattic/jetpack-a8c-mc-stats/zipball/d6bdf2f1d1941e0a22d17c6f3152097d8e0a30e6", + "reference": "d6bdf2f1d1941e0a22d17c6f3152097d8e0a30e6", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.8", + "automattic/jetpack-changelogger": "^5.0.0", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -34,11 +34,11 @@ "extra": { "autotagger": true, "mirror-repo": "Automattic/jetpack-a8c-mc-stats", + "branch-alias": { + "dev-trunk": "3.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-a8c-mc-stats/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -52,31 +52,31 @@ ], "description": "Used to record internal usage stats for Automattic. Not visible to site owners.", "support": { - "source": "https://github.com/Automattic/jetpack-a8c-mc-stats/tree/v2.0.4" + "source": "https://github.com/Automattic/jetpack-a8c-mc-stats/tree/v3.0.0" }, - "time": "2024-11-04T09:23:35+00:00" + "time": "2024-11-14T20:12:50+00:00" }, { "name": "automattic/jetpack-admin-ui", - "version": "v0.4.6", + "version": "v0.5.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-admin-ui.git", - "reference": "a7bef1b075e35e431c0112f97763df9c6196ae39" + "reference": "a0894d34333451089add7b20f70e73b6509d6b6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/a7bef1b075e35e431c0112f97763df9c6196ae39", - "reference": "a7bef1b075e35e431c0112f97763df9c6196ae39", + "url": "https://api.github.com/repos/Automattic/jetpack-admin-ui/zipball/a0894d34333451089add7b20f70e73b6509d6b6d", + "reference": "a0894d34333451089add7b20f70e73b6509d6b6d", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.8", - "automattic/jetpack-logo": "^2.0.5", - "automattic/wordbless": "dev-master", + "automattic/jetpack-changelogger": "^5.1.0", + "automattic/jetpack-logo": "^3.0.0", + "automattic/wordbless": "^0.4.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -85,14 +85,14 @@ "type": "jetpack-library", "extra": { "autotagger": true, - "mirror-repo": "Automattic/jetpack-admin-ui", "textdomain": "jetpack-admin-ui", + "mirror-repo": "Automattic/jetpack-admin-ui", + "branch-alias": { + "dev-trunk": "0.5.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-admin-ui/compare/${old}...${new}" }, - "branch-alias": { - "dev-trunk": "0.4.x-dev" - }, "version-constants": { "::PACKAGE_VERSION": "src/class-admin-menu.php" } @@ -108,31 +108,31 @@ ], "description": "Generic Jetpack wp-admin UI elements", "support": { - "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.4.6" + "source": "https://github.com/Automattic/jetpack-admin-ui/tree/v0.5.1" }, - "time": "2024-11-04T09:23:52+00:00" + "time": "2024-11-25T16:33:45+00:00" }, { "name": "automattic/jetpack-assets", - "version": "v2.3.13", + "version": "v4.0.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-assets.git", - "reference": "c520bffce576c823d7cbc851198201a820b7f981" + "reference": "ca1ebeceeeafb31876a234fa68ea3065b3eab2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/c520bffce576c823d7cbc851198201a820b7f981", - "reference": "c520bffce576c823d7cbc851198201a820b7f981", + "url": "https://api.github.com/repos/Automattic/jetpack-assets/zipball/ca1ebeceeeafb31876a234fa68ea3065b3eab2c3", + "reference": "ca1ebeceeeafb31876a234fa68ea3065b3eab2c3", "shasum": "" }, "require": { - "automattic/jetpack-constants": "^2.0.5", - "php": ">=7.0" + "automattic/jetpack-constants": "^3.0.1", + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.8", - "brain/monkey": "2.6.1", + "automattic/jetpack-changelogger": "^5.1.0", + "brain/monkey": "^2.6.2", "wikimedia/testing-access-wrapper": "^1.0 || ^2.0 || ^3.0", "yoast/phpunit-polyfills": "^1.1.1" }, @@ -142,13 +142,13 @@ "type": "jetpack-library", "extra": { "autotagger": true, - "mirror-repo": "Automattic/jetpack-assets", "textdomain": "jetpack-assets", + "mirror-repo": "Automattic/jetpack-assets", + "branch-alias": { + "dev-trunk": "4.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-assets/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "2.3.x-dev" } }, "autoload": { @@ -165,46 +165,46 @@ ], "description": "Asset management utilities for Jetpack ecosystem packages", "support": { - "source": "https://github.com/Automattic/jetpack-assets/tree/v2.3.13" + "source": "https://github.com/Automattic/jetpack-assets/tree/v4.0.1" }, - "time": "2024-11-04T09:24:17+00:00" + "time": "2024-12-04T19:43:08+00:00" }, { "name": "automattic/jetpack-autoloader", - "version": "v3.0.10", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-autoloader.git", - "reference": "ec4c465ce6a47fb15c15ab0224ec5b1272422d3e" + "reference": "eb6331a5c50a03afd9896ce012e66858de9c49c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/ec4c465ce6a47fb15c15ab0224ec5b1272422d3e", - "reference": "ec4c465ce6a47fb15c15ab0224ec5b1272422d3e", + "url": "https://api.github.com/repos/Automattic/jetpack-autoloader/zipball/eb6331a5c50a03afd9896ce012e66858de9c49c5", + "reference": "eb6331a5c50a03afd9896ce012e66858de9c49c5", "shasum": "" }, "require": { - "composer-plugin-api": "^1.1 || ^2.0", - "php": ">=7.0" + "composer-plugin-api": "^2.2", + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.6", - "composer/composer": "^1.1 || ^2.0", + "automattic/jetpack-changelogger": "^5.1.0", + "composer/composer": "^2.2", "yoast/phpunit-polyfills": "^1.1.1" }, "type": "composer-plugin", "extra": { - "autotagger": true, "class": "Automattic\\Jetpack\\Autoloader\\CustomAutoloaderPlugin", + "autotagger": true, "mirror-repo": "Automattic/jetpack-autoloader", + "branch-alias": { + "dev-trunk": "5.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-autoloader/compare/v${old}...v${new}" }, "version-constants": { "::VERSION": "src/AutoloadGenerator.php" - }, - "branch-alias": { - "dev-trunk": "3.0.x-dev" } }, "autoload": { @@ -229,29 +229,29 @@ "wordpress" ], "support": { - "source": "https://github.com/Automattic/jetpack-autoloader/tree/v3.0.10" + "source": "https://github.com/Automattic/jetpack-autoloader/tree/v5.0.0" }, - "time": "2024-08-26T14:49:14+00:00" + "time": "2024-11-25T16:33:57+00:00" }, { "name": "automattic/jetpack-config", - "version": "v2.0.4", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-config.git", - "reference": "9f075c81bae6fd638e0b3183612cda5cc9e01e06" + "reference": "fc719eff5073634b0c62793b05be913ca634e192" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-config/zipball/9f075c81bae6fd638e0b3183612cda5cc9e01e06", - "reference": "9f075c81bae6fd638e0b3183612cda5cc9e01e06", + "url": "https://api.github.com/repos/Automattic/jetpack-config/zipball/fc719eff5073634b0c62793b05be913ca634e192", + "reference": "fc719eff5073634b0c62793b05be913ca634e192", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.4", + "automattic/jetpack-changelogger": "^5.0.0", "automattic/jetpack-connection": "@dev", "automattic/jetpack-import": "@dev", "automattic/jetpack-jitm": "@dev", @@ -272,14 +272,14 @@ "type": "jetpack-library", "extra": { "autotagger": true, - "mirror-repo": "Automattic/jetpack-config", "textdomain": "jetpack-config", + "mirror-repo": "Automattic/jetpack-config", + "branch-alias": { + "dev-trunk": "3.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-config/compare/v${old}...v${new}" }, - "branch-alias": { - "dev-trunk": "2.0.x-dev" - }, "dependencies": { "test-only": [ "packages/connection", @@ -309,38 +309,38 @@ ], "description": "Jetpack configuration package that initializes other packages and configures Jetpack's functionality. Can be used as a base for all variants of Jetpack package usage.", "support": { - "source": "https://github.com/Automattic/jetpack-config/tree/v2.0.4" + "source": "https://github.com/Automattic/jetpack-config/tree/v3.0.0" }, - "time": "2024-06-24T19:22:07+00:00" + "time": "2024-11-14T20:12:40+00:00" }, { "name": "automattic/jetpack-connection", - "version": "v2.12.4", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-connection.git", - "reference": "35dd5b89b9936555ac185e83a489f41655974e70" + "reference": "52cd2ba7d845eb516d505959bd9a5e94d1bf4203" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/35dd5b89b9936555ac185e83a489f41655974e70", - "reference": "35dd5b89b9936555ac185e83a489f41655974e70", + "url": "https://api.github.com/repos/Automattic/jetpack-connection/zipball/52cd2ba7d845eb516d505959bd9a5e94d1bf4203", + "reference": "52cd2ba7d845eb516d505959bd9a5e94d1bf4203", "shasum": "" }, "require": { - "automattic/jetpack-a8c-mc-stats": "^2.0.2", - "automattic/jetpack-admin-ui": "^0.4.3", - "automattic/jetpack-assets": "^2.3.4", - "automattic/jetpack-constants": "^2.0.4", - "automattic/jetpack-redirect": "^2.0.3", - "automattic/jetpack-roles": "^2.0.3", - "automattic/jetpack-status": "^3.3.4", - "php": ">=7.0" + "automattic/jetpack-a8c-mc-stats": "^3.0.0", + "automattic/jetpack-admin-ui": "^0.5.1", + "automattic/jetpack-assets": "^4.0.1", + "automattic/jetpack-constants": "^3.0.1", + "automattic/jetpack-redirect": "^3.0.1", + "automattic/jetpack-roles": "^3.0.1", + "automattic/jetpack-status": "^5.0.1", + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.6", - "automattic/wordbless": "@dev", - "brain/monkey": "2.6.1", + "automattic/jetpack-changelogger": "^5.1.0", + "automattic/wordbless": "^0.4.2", + "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -349,25 +349,28 @@ "type": "jetpack-library", "extra": { "autotagger": true, - "mirror-repo": "Automattic/jetpack-connection", "textdomain": "jetpack-connection", - "version-constants": { - "::PACKAGE_VERSION": "src/class-package-version.php" + "mirror-repo": "Automattic/jetpack-connection", + "branch-alias": { + "dev-trunk": "6.2.x-dev" }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-connection/compare/v${old}...v${new}" }, - "branch-alias": { - "dev-trunk": "2.12.x-dev" - }, "dependencies": { "test-only": [ "packages/licensing", "packages/sync" ] + }, + "version-constants": { + "::PACKAGE_VERSION": "src/class-package-version.php" } }, "autoload": { + "files": [ + "actions.php" + ], "classmap": [ "legacy", "src/", @@ -381,30 +384,30 @@ ], "description": "Everything needed to connect to the Jetpack infrastructure", "support": { - "source": "https://github.com/Automattic/jetpack-connection/tree/v2.12.4" + "source": "https://github.com/Automattic/jetpack-connection/tree/v6.2.0" }, - "time": "2024-08-23T14:29:32+00:00" + "time": "2024-12-09T15:47:56+00:00" }, { "name": "automattic/jetpack-constants", - "version": "v2.0.5", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-constants.git", - "reference": "0c2644d642b06ae2a31c561f5bfc6f74a4abc8f1" + "reference": "d4b7820defcdb40c1add88d5ebd722e4ba80a873" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/0c2644d642b06ae2a31c561f5bfc6f74a4abc8f1", - "reference": "0c2644d642b06ae2a31c561f5bfc6f74a4abc8f1", + "url": "https://api.github.com/repos/Automattic/jetpack-constants/zipball/d4b7820defcdb40c1add88d5ebd722e4ba80a873", + "reference": "d4b7820defcdb40c1add88d5ebd722e4ba80a873", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.8", - "brain/monkey": "2.6.1", + "automattic/jetpack-changelogger": "^5.1.0", + "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -414,11 +417,11 @@ "extra": { "autotagger": true, "mirror-repo": "Automattic/jetpack-constants", + "branch-alias": { + "dev-trunk": "3.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-constants/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -432,30 +435,30 @@ ], "description": "A wrapper for defining constants in a more testable way.", "support": { - "source": "https://github.com/Automattic/jetpack-constants/tree/v2.0.5" + "source": "https://github.com/Automattic/jetpack-constants/tree/v3.0.1" }, - "time": "2024-11-04T09:23:35+00:00" + "time": "2024-11-25T16:33:27+00:00" }, { "name": "automattic/jetpack-ip", - "version": "v0.2.3", + "version": "v0.4.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-ip.git", - "reference": "f7a42b1603a24775c6f20eef2ac5cba3d6b37194" + "reference": "04d7deb2c16faa6c4a3e5074bf0e12c8a87d035a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-ip/zipball/f7a42b1603a24775c6f20eef2ac5cba3d6b37194", - "reference": "f7a42b1603a24775c6f20eef2ac5cba3d6b37194", + "url": "https://api.github.com/repos/Automattic/jetpack-ip/zipball/04d7deb2c16faa6c4a3e5074bf0e12c8a87d035a", + "reference": "04d7deb2c16faa6c4a3e5074bf0e12c8a87d035a", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.6", - "brain/monkey": "2.6.1", + "automattic/jetpack-changelogger": "^5.1.0", + "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -464,14 +467,14 @@ "type": "jetpack-library", "extra": { "autotagger": true, + "textdomain": "jetpack-ip", "mirror-repo": "Automattic/jetpack-ip", + "branch-alias": { + "dev-trunk": "0.4.x-dev" + }, "changelogger": { "link-template": "https://github.com/automattic/jetpack-ip/compare/v${old}...v${new}" }, - "branch-alias": { - "dev-trunk": "0.2.x-dev" - }, - "textdomain": "jetpack-ip", "version-constants": { "::PACKAGE_VERSION": "src/class-utils.php" } @@ -487,30 +490,30 @@ ], "description": "Utilities for working with IP addresses.", "support": { - "source": "https://github.com/Automattic/jetpack-ip/tree/v0.2.3" + "source": "https://github.com/Automattic/jetpack-ip/tree/v0.4.1" }, - "time": "2024-08-23T14:28:05+00:00" + "time": "2024-11-25T16:33:22+00:00" }, { "name": "automattic/jetpack-password-checker", - "version": "v0.3.3", + "version": "v0.4.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-password-checker.git", - "reference": "1812a38452575e7c8c7c06affeeca776a367225f" + "reference": "e721e7659cc7a6a37152a4e96485e6c139f02d5f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-password-checker/zipball/1812a38452575e7c8c7c06affeeca776a367225f", - "reference": "1812a38452575e7c8c7c06affeeca776a367225f", + "url": "https://api.github.com/repos/Automattic/jetpack-password-checker/zipball/e721e7659cc7a6a37152a4e96485e6c139f02d5f", + "reference": "e721e7659cc7a6a37152a4e96485e6c139f02d5f", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.8", - "automattic/wordbless": "@dev", + "automattic/jetpack-changelogger": "^5.1.0", + "automattic/wordbless": "^0.4.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -519,13 +522,13 @@ "type": "jetpack-library", "extra": { "autotagger": true, - "mirror-repo": "Automattic/jetpack-password-checker", "textdomain": "jetpack-password-checker", + "mirror-repo": "Automattic/jetpack-password-checker", + "branch-alias": { + "dev-trunk": "0.4.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-password-checker/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "0.3.x-dev" } }, "autoload": { @@ -539,31 +542,31 @@ ], "description": "Password Checker.", "support": { - "source": "https://github.com/Automattic/jetpack-password-checker/tree/v0.3.3" + "source": "https://github.com/Automattic/jetpack-password-checker/tree/v0.4.1" }, - "time": "2024-11-04T09:23:39+00:00" + "time": "2024-11-25T16:33:31+00:00" }, { "name": "automattic/jetpack-redirect", - "version": "v2.0.3", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-redirect.git", - "reference": "2c049bb08f736dc0dbafac7eaebea6f97cf8019e" + "reference": "89732a3ba1c5eba8cfd948b7567823cd884102d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-redirect/zipball/2c049bb08f736dc0dbafac7eaebea6f97cf8019e", - "reference": "2c049bb08f736dc0dbafac7eaebea6f97cf8019e", + "url": "https://api.github.com/repos/Automattic/jetpack-redirect/zipball/89732a3ba1c5eba8cfd948b7567823cd884102d5", + "reference": "89732a3ba1c5eba8cfd948b7567823cd884102d5", "shasum": "" }, "require": { - "automattic/jetpack-status": "^3.3.4", - "php": ">=7.0" + "automattic/jetpack-status": "^5.0.1", + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.6", - "brain/monkey": "2.6.1", + "automattic/jetpack-changelogger": "^5.1.0", + "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -573,11 +576,11 @@ "extra": { "autotagger": true, "mirror-repo": "Automattic/jetpack-redirect", + "branch-alias": { + "dev-trunk": "3.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-redirect/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -591,30 +594,30 @@ ], "description": "Utilities to build URLs to the jetpack.com/redirect/ service", "support": { - "source": "https://github.com/Automattic/jetpack-redirect/tree/v2.0.3" + "source": "https://github.com/Automattic/jetpack-redirect/tree/v3.0.1" }, - "time": "2024-08-23T14:28:46+00:00" + "time": "2024-11-25T16:34:01+00:00" }, { "name": "automattic/jetpack-roles", - "version": "v2.0.4", + "version": "v3.0.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-roles.git", - "reference": "2fa5361ce8ff271cc4ecfac5be9b957ab0e9912f" + "reference": "fe5f2a45901ea14be00728119d097619615fb031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/2fa5361ce8ff271cc4ecfac5be9b957ab0e9912f", - "reference": "2fa5361ce8ff271cc4ecfac5be9b957ab0e9912f", + "url": "https://api.github.com/repos/Automattic/jetpack-roles/zipball/fe5f2a45901ea14be00728119d097619615fb031", + "reference": "fe5f2a45901ea14be00728119d097619615fb031", "shasum": "" }, "require": { - "php": ">=7.0" + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.8", - "brain/monkey": "2.6.1", + "automattic/jetpack-changelogger": "^5.1.0", + "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -624,11 +627,11 @@ "extra": { "autotagger": true, "mirror-repo": "Automattic/jetpack-roles", + "branch-alias": { + "dev-trunk": "3.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-roles/compare/v${old}...v${new}" - }, - "branch-alias": { - "dev-trunk": "2.0.x-dev" } }, "autoload": { @@ -642,34 +645,34 @@ ], "description": "Utilities, related with user roles and capabilities.", "support": { - "source": "https://github.com/Automattic/jetpack-roles/tree/v2.0.4" + "source": "https://github.com/Automattic/jetpack-roles/tree/v3.0.1" }, - "time": "2024-11-04T09:23:38+00:00" + "time": "2024-11-25T16:33:29+00:00" }, { "name": "automattic/jetpack-status", - "version": "v3.3.5", + "version": "v5.0.1", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-status.git", - "reference": "69d5d8a8f31adf2b297a539bcddd9a9162d1320b" + "reference": "769f55b6327187a85c14ed21943eea430f63220d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/69d5d8a8f31adf2b297a539bcddd9a9162d1320b", - "reference": "69d5d8a8f31adf2b297a539bcddd9a9162d1320b", + "url": "https://api.github.com/repos/Automattic/jetpack-status/zipball/769f55b6327187a85c14ed21943eea430f63220d", + "reference": "769f55b6327187a85c14ed21943eea430f63220d", "shasum": "" }, "require": { - "automattic/jetpack-constants": "^2.0.4", - "php": ">=7.0" + "automattic/jetpack-constants": "^3.0.1", + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.6", + "automattic/jetpack-changelogger": "^5.1.0", "automattic/jetpack-connection": "@dev", - "automattic/jetpack-ip": "^0.2.3", + "automattic/jetpack-ip": "^0.4.1", "automattic/jetpack-plans": "@dev", - "brain/monkey": "2.6.1", + "brain/monkey": "^2.6.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -679,12 +682,12 @@ "extra": { "autotagger": true, "mirror-repo": "Automattic/jetpack-status", + "branch-alias": { + "dev-trunk": "5.0.x-dev" + }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-status/compare/v${old}...v${new}" }, - "branch-alias": { - "dev-trunk": "3.3.x-dev" - }, "dependencies": { "test-only": [ "packages/connection", @@ -703,38 +706,38 @@ ], "description": "Used to retrieve information about the current status of Jetpack and the site overall.", "support": { - "source": "https://github.com/Automattic/jetpack-status/tree/v3.3.5" + "source": "https://github.com/Automattic/jetpack-status/tree/v5.0.1" }, - "time": "2024-09-10T17:55:40+00:00" + "time": "2024-11-25T16:33:53+00:00" }, { "name": "automattic/jetpack-sync", - "version": "v3.8.0", + "version": "v4.1.0", "source": { "type": "git", "url": "https://github.com/Automattic/jetpack-sync.git", - "reference": "30b29f0c5a27e01cbf2fa592fbde97f617665153" + "reference": "5747f144575b9474622692f2bc8e4315363ea44d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Automattic/jetpack-sync/zipball/30b29f0c5a27e01cbf2fa592fbde97f617665153", - "reference": "30b29f0c5a27e01cbf2fa592fbde97f617665153", + "url": "https://api.github.com/repos/Automattic/jetpack-sync/zipball/5747f144575b9474622692f2bc8e4315363ea44d", + "reference": "5747f144575b9474622692f2bc8e4315363ea44d", "shasum": "" }, "require": { - "automattic/jetpack-connection": "^2.12.4", - "automattic/jetpack-constants": "^2.0.4", - "automattic/jetpack-ip": "^0.2.3", - "automattic/jetpack-password-checker": "^0.3.2", - "automattic/jetpack-roles": "^2.0.3", - "automattic/jetpack-status": "^3.3.4", - "php": ">=7.0" + "automattic/jetpack-connection": "^6.2.0", + "automattic/jetpack-constants": "^3.0.1", + "automattic/jetpack-ip": "^0.4.1", + "automattic/jetpack-password-checker": "^0.4.1", + "automattic/jetpack-roles": "^3.0.1", + "automattic/jetpack-status": "^5.0.1", + "php": ">=7.2" }, "require-dev": { - "automattic/jetpack-changelogger": "^4.2.6", + "automattic/jetpack-changelogger": "^5.1.0", "automattic/jetpack-search": "@dev", - "automattic/jetpack-waf": "^0.18.4", - "automattic/wordbless": "@dev", + "automattic/jetpack-waf": "^0.23.1", + "automattic/wordbless": "^0.4.2", "yoast/phpunit-polyfills": "^1.1.1" }, "suggest": { @@ -743,22 +746,22 @@ "type": "jetpack-library", "extra": { "autotagger": true, - "mirror-repo": "Automattic/jetpack-sync", "textdomain": "jetpack-sync", - "version-constants": { - "::PACKAGE_VERSION": "src/class-package-version.php" + "mirror-repo": "Automattic/jetpack-sync", + "branch-alias": { + "dev-trunk": "4.1.x-dev" }, "changelogger": { "link-template": "https://github.com/Automattic/jetpack-sync/compare/v${old}...v${new}" }, - "branch-alias": { - "dev-trunk": "3.8.x-dev" - }, "dependencies": { "test-only": [ "packages/search", "packages/waf" ] + }, + "version-constants": { + "::PACKAGE_VERSION": "src/class-package-version.php" } }, "autoload": { @@ -772,9 +775,9 @@ ], "description": "Everything needed to allow syncing to the WP.com infrastructure.", "support": { - "source": "https://github.com/Automattic/jetpack-sync/tree/v3.8.0" + "source": "https://github.com/Automattic/jetpack-sync/tree/v4.1.0" }, - "time": "2024-08-26T14:49:56+00:00" + "time": "2024-12-09T15:48:10+00:00" }, { "name": "composer/installers", diff --git a/woocommerce-payments.php b/woocommerce-payments.php index 19012d26053..e69ae1934ad 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -78,6 +78,12 @@ function wcpay_jetpack_init() { if ( ! wcpay_check_old_jetpack_version() ) { return; } + $connection_version = Automattic\Jetpack\Connection\Package_Version::PACKAGE_VERSION; + + $custom_content = version_compare( $connection_version, '6.1.0', '>' ) ? + 'wcpay_get_jetpack_idc_custom_content' : + wcpay_get_jetpack_idc_custom_content(); + $jetpack_config = new Automattic\Jetpack\Config(); $jetpack_config->ensure( 'connection', @@ -90,7 +96,7 @@ function wcpay_jetpack_init() { 'identity_crisis', [ 'slug' => 'woocommerce-payments', - 'customContent' => wcpay_get_jetpack_idc_custom_content(), + 'customContent' => $custom_content, 'logo' => plugins_url( 'assets/images/logo.svg', WCPAY_PLUGIN_FILE ), 'admin_page' => '/wp-admin/admin.php?page=wc-admin', 'priority' => 5, From 4608ebb2317244de7bad6fc92fc94179be8547f5 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Fri, 13 Dec 2024 18:23:10 +0200 Subject: [PATCH 43/83] Compatibility Fix: Do not load translations early (#9940) Co-authored-by: Igor Zinovyev Co-authored-by: Cvetan Cvetanov --- .../compat-9727-avoid-early-translations | 4 + includes/admin/class-wc-payments-admin.php | 86 ++++++++--------- includes/class-wc-payment-gateway-wcpay.php | 96 +++++++++++-------- .../class-affirm-payment-method.php | 13 ++- .../class-afterpay-payment-method.php | 1 - .../class-cc-payment-method.php | 3 +- .../class-klarna-payment-method.php | 13 ++- .../class-link-payment-method.php | 13 ++- .../admin/test-class-wc-payments-admin.php | 2 + 9 files changed, 142 insertions(+), 89 deletions(-) create mode 100644 changelog/compat-9727-avoid-early-translations diff --git a/changelog/compat-9727-avoid-early-translations b/changelog/compat-9727-avoid-early-translations new file mode 100644 index 00000000000..51432b8cd10 --- /dev/null +++ b/changelog/compat-9727-avoid-early-translations @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Remove translations during initialization, preventing unnecessary warnings. diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 01cce6c775e..6509cc90ecb 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -144,49 +144,6 @@ public function __construct( $this->incentives_service = $incentives_service; $this->fraud_service = $fraud_service; $this->database_cache = $database_cache; - - $this->admin_child_pages = [ - 'wc-payments-overview' => [ - 'id' => 'wc-payments-overview', - 'title' => __( 'Overview', 'woocommerce-payments' ), - 'parent' => 'wc-payments', - 'path' => '/payments/overview', - 'nav_args' => [ - 'parent' => 'wc-payments', - 'order' => 10, - ], - ], - 'wc-payments-deposits' => [ - 'id' => 'wc-payments-deposits', - 'title' => __( 'Payouts', 'woocommerce-payments' ), - 'parent' => 'wc-payments', - 'path' => '/payments/payouts', - 'nav_args' => [ - 'parent' => 'wc-payments', - 'order' => 20, - ], - ], - 'wc-payments-transactions' => [ - 'id' => 'wc-payments-transactions', - 'title' => __( 'Transactions', 'woocommerce-payments' ), - 'parent' => 'wc-payments', - 'path' => '/payments/transactions', - 'nav_args' => [ - 'parent' => 'wc-payments', - 'order' => 30, - ], - ], - 'wc-payments-disputes' => [ - 'id' => 'wc-payments-disputes', - 'title' => __( 'Disputes', 'woocommerce-payments' ), - 'parent' => 'wc-payments', - 'path' => '/payments/disputes', - 'nav_args' => [ - 'parent' => 'wc-payments', - 'order' => 40, - ], - ], - ]; } /** @@ -315,6 +272,49 @@ public function add_payments_menu() { } global $submenu; + $this->admin_child_pages = [ + 'wc-payments-overview' => [ + 'id' => 'wc-payments-overview', + 'title' => __( 'Overview', 'woocommerce-payments' ), + 'parent' => 'wc-payments', + 'path' => '/payments/overview', + 'nav_args' => [ + 'parent' => 'wc-payments', + 'order' => 10, + ], + ], + 'wc-payments-deposits' => [ + 'id' => 'wc-payments-deposits', + 'title' => __( 'Payouts', 'woocommerce-payments' ), + 'parent' => 'wc-payments', + 'path' => '/payments/payouts', + 'nav_args' => [ + 'parent' => 'wc-payments', + 'order' => 20, + ], + ], + 'wc-payments-transactions' => [ + 'id' => 'wc-payments-transactions', + 'title' => __( 'Transactions', 'woocommerce-payments' ), + 'parent' => 'wc-payments', + 'path' => '/payments/transactions', + 'nav_args' => [ + 'parent' => 'wc-payments', + 'order' => 30, + ], + ], + 'wc-payments-disputes' => [ + 'id' => 'wc-payments-disputes', + 'title' => __( 'Disputes', 'woocommerce-payments' ), + 'parent' => 'wc-payments', + 'path' => '/payments/disputes', + 'nav_args' => [ + 'parent' => 'wc-payments', + 'order' => 40, + ], + ], + ]; + try { // Render full payments menu with sub-items only if: // - we have working WPCOM/Jetpack connection; diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index cff7022a1b9..3e02aa6acf5 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -309,13 +309,11 @@ public function __construct( $this->fraud_service = $fraud_service; $this->duplicate_payment_methods_detection_service = $duplicate_payment_methods_detection_service; - $this->id = static::GATEWAY_ID; - $this->icon = $this->get_theme_icon(); - $this->has_fields = true; - $this->method_title = 'WooPayments'; - $this->method_description = $this->get_method_description(); + $this->id = static::GATEWAY_ID; + $this->icon = $this->get_theme_icon(); + $this->has_fields = true; + $this->method_title = 'WooPayments'; - $this->title = $payment_method->get_title(); $this->description = ''; $this->supports = [ 'products', @@ -327,7 +325,57 @@ public function __construct( $this->method_title = "WooPayments ($this->title)"; } - // Define setting fields. + // Capabilities have different keys than the payment method ID's, + // so instead of appending '_payments' to the end of the ID, it'll be better + // to have a map for it instead, just in case the pattern changes. + $this->payment_method_capability_key_map = [ + 'sofort' => 'sofort_payments', + 'giropay' => 'giropay_payments', + 'bancontact' => 'bancontact_payments', + 'eps' => 'eps_payments', + 'ideal' => 'ideal_payments', + 'p24' => 'p24_payments', + 'card' => 'card_payments', + 'sepa_debit' => 'sepa_debit_payments', + 'au_becs_debit' => 'au_becs_debit_payments', + 'link' => 'link_payments', + 'affirm' => 'affirm_payments', + 'afterpay_clearpay' => 'afterpay_clearpay_payments', + 'klarna' => 'klarna_payments', + 'jcb' => 'jcb_payments', + ]; + + // WooPay utilities. + $this->woopay_util = new WooPay_Utilities(); + + // Load the settings. + $this->init_settings(); + + // Check if subscriptions are enabled and add support for them. + $this->maybe_init_subscriptions(); + + // If the setting to enable saved cards is enabled, then we should support tokenization and adding payment methods. + if ( $this->is_saved_cards_enabled() ) { + array_push( $this->supports, 'tokenization', 'add_payment_method' ); + } + } + + /** + * Return the gateway's title. + * + * @return string + */ + public function get_title() { + $this->title = $this->payment_method->get_title(); + return parent::get_title(); + } + + /** + * Get the form fields after they are initialized. + * + * @return array of options + */ + public function get_form_fields() { $this->form_fields = [ 'enabled' => [ 'title' => __( 'Enable/disable', 'woocommerce-payments' ), @@ -497,39 +545,7 @@ public function __construct( 'platform_checkout_custom_message' => [ 'default' => __( 'By placing this order, you agree to our [terms] and understand our [privacy_policy].', 'woocommerce-payments' ) ], ]; - // Capabilities have different keys than the payment method ID's, - // so instead of appending '_payments' to the end of the ID, it'll be better - // to have a map for it instead, just in case the pattern changes. - $this->payment_method_capability_key_map = [ - 'sofort' => 'sofort_payments', - 'giropay' => 'giropay_payments', - 'bancontact' => 'bancontact_payments', - 'eps' => 'eps_payments', - 'ideal' => 'ideal_payments', - 'p24' => 'p24_payments', - 'card' => 'card_payments', - 'sepa_debit' => 'sepa_debit_payments', - 'au_becs_debit' => 'au_becs_debit_payments', - 'link' => 'link_payments', - 'affirm' => 'affirm_payments', - 'afterpay_clearpay' => 'afterpay_clearpay_payments', - 'klarna' => 'klarna_payments', - 'jcb' => 'jcb_payments', - ]; - - // WooPay utilities. - $this->woopay_util = new WooPay_Utilities(); - - // Load the settings. - $this->init_settings(); - - // Check if subscriptions are enabled and add support for them. - $this->maybe_init_subscriptions(); - - // If the setting to enable saved cards is enabled, then we should support tokenization and adding payment methods. - if ( $this->is_saved_cards_enabled() ) { - array_push( $this->supports, 'tokenization', 'add_payment_method' ); - } + return parent::get_form_fields(); } /** diff --git a/includes/payment-methods/class-affirm-payment-method.php b/includes/payment-methods/class-affirm-payment-method.php index 47b89f49951..1c87c67149f 100644 --- a/includes/payment-methods/class-affirm-payment-method.php +++ b/includes/payment-methods/class-affirm-payment-method.php @@ -27,7 +27,6 @@ class Affirm_Payment_Method extends UPE_Payment_Method { public function __construct( $token_service ) { parent::__construct( $token_service ); $this->stripe_id = self::PAYMENT_METHOD_STRIPE_ID; - $this->title = __( 'Affirm', 'woocommerce-payments' ); $this->is_reusable = false; $this->is_bnpl = true; $this->icon_url = plugins_url( 'assets/images/payment-methods/affirm-logo.svg', WCPAY_PLUGIN_FILE ); @@ -38,6 +37,18 @@ public function __construct( $token_service ) { $this->countries = [ Country_Code::UNITED_STATES, Country_Code::CANADA ]; } + /** + * Returns payment method title + * + * @param string|null $account_country Country of merchants account. + * @param array|false $payment_details Optional payment details from charge object. + * + * @return string + */ + public function get_title( ?string $account_country = null, $payment_details = false ) { + return __( 'Affirm', 'woocommerce-payments' ); + } + /** * Returns testing credentials to be printed at checkout in test mode. * diff --git a/includes/payment-methods/class-afterpay-payment-method.php b/includes/payment-methods/class-afterpay-payment-method.php index 3674731835c..503f0c6104d 100644 --- a/includes/payment-methods/class-afterpay-payment-method.php +++ b/includes/payment-methods/class-afterpay-payment-method.php @@ -27,7 +27,6 @@ class Afterpay_Payment_Method extends UPE_Payment_Method { public function __construct( $token_service ) { parent::__construct( $token_service ); $this->stripe_id = self::PAYMENT_METHOD_STRIPE_ID; - $this->title = __( 'Afterpay', 'woocommerce-payments' ); $this->is_reusable = false; $this->is_bnpl = true; $this->icon_url = plugins_url( 'assets/images/payment-methods/afterpay-logo.svg', WCPAY_PLUGIN_FILE ); diff --git a/includes/payment-methods/class-cc-payment-method.php b/includes/payment-methods/class-cc-payment-method.php index 50a44fa1114..58d7d733a77 100644 --- a/includes/payment-methods/class-cc-payment-method.php +++ b/includes/payment-methods/class-cc-payment-method.php @@ -25,7 +25,6 @@ class CC_Payment_Method extends UPE_Payment_Method { public function __construct( $token_service ) { parent::__construct( $token_service ); $this->stripe_id = self::PAYMENT_METHOD_STRIPE_ID; - $this->title = __( 'Credit card / debit card', 'woocommerce-payments' ); $this->is_reusable = true; $this->currencies = [];// All currencies are supported. $this->icon_url = plugins_url( 'assets/images/payment-methods/generic-card.svg', WCPAY_PLUGIN_FILE ); @@ -40,7 +39,7 @@ public function __construct( $token_service ) { */ public function get_title( ?string $account_country = null, $payment_details = false ) { if ( ! $payment_details ) { - return $this->title; + return __( 'Credit card / debit card', 'woocommerce-payments' ); } $details = $payment_details[ $this->stripe_id ]; diff --git a/includes/payment-methods/class-klarna-payment-method.php b/includes/payment-methods/class-klarna-payment-method.php index 31c71cb813a..27495db4b02 100644 --- a/includes/payment-methods/class-klarna-payment-method.php +++ b/includes/payment-methods/class-klarna-payment-method.php @@ -27,7 +27,6 @@ class Klarna_Payment_Method extends UPE_Payment_Method { public function __construct( $token_service ) { parent::__construct( $token_service ); $this->stripe_id = self::PAYMENT_METHOD_STRIPE_ID; - $this->title = __( 'Klarna', 'woocommerce-payments' ); $this->is_reusable = false; $this->is_bnpl = true; $this->icon_url = plugins_url( 'assets/images/payment-methods/klarna-pill.svg', WCPAY_PLUGIN_FILE ); @@ -37,6 +36,18 @@ public function __construct( $token_service ) { $this->limits_per_currency = WC_Payments_Utils::get_bnpl_limits_per_currency( self::PAYMENT_METHOD_STRIPE_ID ); } + /** + * Returns payment method title + * + * @param string|null $account_country Country of merchants account. + * @param array|false $payment_details Optional payment details from charge object. + * + * @return string + */ + public function get_title( ?string $account_country = null, $payment_details = false ) { + return __( 'Klarna', 'woocommerce-payments' ); + } + /** * Returns payment method supported countries. * diff --git a/includes/payment-methods/class-link-payment-method.php b/includes/payment-methods/class-link-payment-method.php index c5c189bbad8..0e086cd7e86 100644 --- a/includes/payment-methods/class-link-payment-method.php +++ b/includes/payment-methods/class-link-payment-method.php @@ -25,12 +25,23 @@ class Link_Payment_Method extends UPE_Payment_Method { public function __construct( $token_service ) { parent::__construct( $token_service ); $this->stripe_id = self::PAYMENT_METHOD_STRIPE_ID; - $this->title = __( 'Link', 'woocommerce-payments' ); $this->is_reusable = true; $this->currencies = [ Currency_Code::UNITED_STATES_DOLLAR ]; $this->icon_url = plugins_url( 'assets/images/payment-methods/link.svg', WCPAY_PLUGIN_FILE ); } + /** + * Returns payment method title + * + * @param string|null $account_country Country of merchants account. + * @param array|false $payment_details Optional payment details from charge object. + * + * @return string + */ + public function get_title( ?string $account_country = null, $payment_details = false ) { + return __( 'Link', 'woocommerce-payments' ); + } + /** * Returns testing credentials to be printed at checkout in test mode. * diff --git a/tests/unit/admin/test-class-wc-payments-admin.php b/tests/unit/admin/test-class-wc-payments-admin.php index 6a16577f18c..6dba99d9d1b 100644 --- a/tests/unit/admin/test-class-wc-payments-admin.php +++ b/tests/unit/admin/test-class-wc-payments-admin.php @@ -203,6 +203,8 @@ private function mock_current_user_is_admin() { */ public function test_maybe_redirect_from_payments_admin_child_pages( $expected_times_redirect_called, $has_working_jetpack_connection, $is_stripe_account_valid, $get_params ) { $this->mock_current_user_is_admin(); + $this->payments_admin->add_payments_menu(); + $_GET = $get_params; $this->mock_account From 5064ccbd23bfc9005fcb4c34405922577bf55aa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar=20Costa?= <10233985+cesarcosta99@users.noreply.github.com> Date: Fri, 13 Dec 2024 13:46:29 -0300 Subject: [PATCH 44/83] Add login confirmation check to ECE in Blocks (#9944) --- changelog/fix-9806-ECE-subscription-checkout-signed-out | 4 ++++ .../express-checkout/blocks/hooks/use-express-checkout.js | 7 +++++++ .../blocks/hooks/use-express-checkout.js | 7 +++++++ 3 files changed, 18 insertions(+) create mode 100644 changelog/fix-9806-ECE-subscription-checkout-signed-out diff --git a/changelog/fix-9806-ECE-subscription-checkout-signed-out b/changelog/fix-9806-ECE-subscription-checkout-signed-out new file mode 100644 index 00000000000..fa25afd1f10 --- /dev/null +++ b/changelog/fix-9806-ECE-subscription-checkout-signed-out @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ensure ECE login confirmation dialog is shown on Blocks. diff --git a/client/express-checkout/blocks/hooks/use-express-checkout.js b/client/express-checkout/blocks/hooks/use-express-checkout.js index 962f74e5876..e2d68bc6bce 100644 --- a/client/express-checkout/blocks/hooks/use-express-checkout.js +++ b/client/express-checkout/blocks/hooks/use-express-checkout.js @@ -8,6 +8,7 @@ import { useStripe, useElements } from '@stripe/react-stripe-js'; * Internal dependencies */ import { + displayLoginConfirmation, getExpressCheckoutButtonStyleSettings, getExpressCheckoutData, normalizeLineItems, @@ -52,6 +53,12 @@ export const useExpressCheckout = ( { const onButtonClick = useCallback( ( event ) => { + // If login is required for checkout, display redirect confirmation dialog. + if ( getExpressCheckoutData( 'login_confirmation' ) ) { + displayLoginConfirmation( event.expressPaymentType ); + return; + } + const options = { lineItems: normalizeLineItems( billing?.cartTotalItems ), emailRequired: true, diff --git a/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js b/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js index f33604354f7..afdcca3f6d2 100644 --- a/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js +++ b/client/tokenized-express-checkout/blocks/hooks/use-express-checkout.js @@ -8,6 +8,7 @@ import { useStripe, useElements } from '@stripe/react-stripe-js'; * Internal dependencies */ import { + displayLoginConfirmation, getExpressCheckoutButtonStyleSettings, getExpressCheckoutData, normalizeLineItems, @@ -52,6 +53,12 @@ export const useExpressCheckout = ( { const onButtonClick = useCallback( ( event ) => { + // If login is required for checkout, display redirect confirmation dialog. + if ( getExpressCheckoutData( 'login_confirmation' ) ) { + displayLoginConfirmation( event.expressPaymentType ); + return; + } + const options = { lineItems: normalizeLineItems( billing?.cartTotalItems ), emailRequired: true, From 6537f031c85fa5a62ef41df52116eb1fce31d031 Mon Sep 17 00:00:00 2001 From: Francesco Date: Fri, 13 Dec 2024 18:30:14 +0100 Subject: [PATCH 45/83] fix: tokenized cart error notice json (#9950) --- .../fix-tokenized-cart-error-notice-json | 5 ++++ .../event-handlers.js | 23 ++++++++++++------- .../tokenized-express-checkout/utils/index.ts | 4 +++- 3 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 changelog/fix-tokenized-cart-error-notice-json diff --git a/changelog/fix-tokenized-cart-error-notice-json b/changelog/fix-tokenized-cart-error-notice-json new file mode 100644 index 00000000000..c132e0f7eeb --- /dev/null +++ b/changelog/fix-tokenized-cart-error-notice-json @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: fix: tokenized cart error notice json + + diff --git a/client/tokenized-express-checkout/event-handlers.js b/client/tokenized-express-checkout/event-handlers.js index cc300e36ef5..12ec2513cc7 100644 --- a/client/tokenized-express-checkout/event-handlers.js +++ b/client/tokenized-express-checkout/event-handlers.js @@ -149,16 +149,23 @@ export const onConfirmHandler = async ( completePayment( redirectUrl ); } } catch ( e ) { + // API errors are not parsed, so we need to do it ourselves. + if ( e.json ) { + e = e.json(); + } + return abortPayment( event, - getErrorMessageFromNotice( e.message ) || - e.payment_result?.payment_details.find( - ( detail ) => detail.key === 'errorMessage' - )?.value || - __( - 'There was a problem processing the order.', - 'woocommerce-payments' - ) + getErrorMessageFromNotice( + e.message || + e.payment_result?.payment_details.find( + ( detail ) => detail.key === 'errorMessage' + )?.value || + __( + 'There was a problem processing the order.', + 'woocommerce-payments' + ) + ) ); } }; diff --git a/client/tokenized-express-checkout/utils/index.ts b/client/tokenized-express-checkout/utils/index.ts index 9b92ec023ba..98ee8b90091 100644 --- a/client/tokenized-express-checkout/utils/index.ts +++ b/client/tokenized-express-checkout/utils/index.ts @@ -27,7 +27,9 @@ export const getExpressCheckoutData = < * @param notice Error notice. * @return Error messages. */ -export const getErrorMessageFromNotice = ( notice: string ) => { +export const getErrorMessageFromNotice = ( notice: string | undefined ) => { + if ( ! notice ) return ''; + const div = document.createElement( 'div' ); div.innerHTML = notice.trim(); return div.firstChild ? div.firstChild.textContent : ''; From 5e3b32e09b5379fb82a910852209438861700b6d Mon Sep 17 00:00:00 2001 From: Hector Lovo Date: Fri, 13 Dec 2024 16:28:18 -0500 Subject: [PATCH 46/83] =?UTF-8?q?Ensure=20WooPay=20=E2=80=98Enabled=20by?= =?UTF-8?q?=20Default=E2=80=99=20Value=20Is=20Correctly=20Set=20In=20Sandb?= =?UTF-8?q?ox=20Mode=20(#9898)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ix-9421-auto-enable-woopay-in-sandbox-mode | 4 ++ client/connect-account-page/index.tsx | 18 +++-- includes/class-wc-payments-account.php | 15 ++-- tests/unit/test-class-wc-payments-account.php | 70 +++++++++++++++++++ 4 files changed, 97 insertions(+), 10 deletions(-) create mode 100644 changelog/fix-9421-auto-enable-woopay-in-sandbox-mode diff --git a/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode b/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode new file mode 100644 index 00000000000..30ec0c7fed5 --- /dev/null +++ b/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. diff --git a/client/connect-account-page/index.tsx b/client/connect-account-page/index.tsx index faa5d94311c..6502975d642 100644 --- a/client/connect-account-page/index.tsx +++ b/client/connect-account-page/index.tsx @@ -166,7 +166,7 @@ const ConnectAccountPage: React.FC = () => { } }; - const checkAccountStatus = () => { + const checkAccountStatus = ( extraQueryArgs = {} ) => { // Fetch account status from the cache. apiFetch( { path: `/wc/v3/payments/accounts`, @@ -188,18 +188,22 @@ const ConnectAccountPage: React.FC = () => { loaderProgressRef.current > 95 ) { setTestDriveLoaderProgress( 100 ); - - // Redirect to the Connect URL and let it figure it out where to point the merchant. - window.location.href = addQueryArgs( connectUrl, { + const queryArgs = { test_drive: 'true', 'wcpay-sandbox-success': 'true', source: determineTrackingSource(), from: 'WCPAY_CONNECT', redirect_to_settings_page: urlParams.get( 'redirect_to_settings_page' ) || '', + }; + + // Redirect to the Connect URL and let it figure it out where to point the merchant. + window.location.href = addQueryArgs( connectUrl, { + ...queryArgs, + ...extraQueryArgs, } ); } else { - setTimeout( checkAccountStatus, 2000 ); + setTimeout( () => checkAccountStatus( extraQueryArgs ), 2000 ); } } ); }; @@ -264,7 +268,9 @@ const ConnectAccountPage: React.FC = () => { // The account has been successfully onboarded. if ( !! connectionSuccess ) { // Start checking the account status in a loop. - checkAccountStatus(); + checkAccountStatus( { + 'wcpay-connection-success': '1', + } ); } else { // Redirect to the response URL, but attach our test drive flags. // This URL is generally a Connect page URL. diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 611c3a8acd2..097ef468710 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -2009,9 +2009,19 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne $collect_payout_requirements ); + $should_enable_woopay = filter_var( $onboarding_data['woopay_enabled_by_default'] ?? false, FILTER_VALIDATE_BOOLEAN ); + $is_test_mode = in_array( $setup_mode, [ 'test', 'test_drive' ], true ); + $account_already_exists = isset( $onboarding_data['url'] ) && false === $onboarding_data['url']; + + // Only store the 'woopay_enabled_by_default' flag in a transient, to be enabled later, if + // it should be enabled and the account doesn't already exist, or we are in test mode. + if ( $should_enable_woopay && ( ! $account_already_exists || $is_test_mode ) ) { + set_transient( self::WOOPAY_ENABLED_BY_DEFAULT_TRANSIENT, $should_enable_woopay, DAY_IN_SECONDS ); + } + // If an account already exists for this site and/or there is no need for KYC verifications, we're done. // Our platform will respond with a `false` URL in this case. - if ( isset( $onboarding_data['url'] ) && false === $onboarding_data['url'] ) { + if ( $account_already_exists ) { // Set the gateway options. $gateway = WC_Payments::get_gateway(); $gateway->update_option( 'enabled', 'yes' ); @@ -2032,9 +2042,6 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne ); } - // We have an account that needs to be verified (has a URL to redirect the merchant to). - // Store the relevant onboarding data. - set_transient( self::WOOPAY_ENABLED_BY_DEFAULT_TRANSIENT, filter_var( $onboarding_data['woopay_enabled_by_default'] ?? false, FILTER_VALIDATE_BOOLEAN ), DAY_IN_SECONDS ); // Save the onboarding state for a day. // This is used to verify the state when finalizing the onboarding and connecting the account. // On finalizing the onboarding, the transient gets deleted. diff --git a/tests/unit/test-class-wc-payments-account.php b/tests/unit/test-class-wc-payments-account.php index 934a7a72ada..d46a32722af 100644 --- a/tests/unit/test-class-wc-payments-account.php +++ b/tests/unit/test-class-wc-payments-account.php @@ -907,6 +907,76 @@ public function test_maybe_handle_onboarding_init_embedded_kyc() { $this->wcpay_account->maybe_handle_onboarding(); } + public function test_ensure_woopay_enabled_by_default_value_set_in_sandbox_mode_kyc() { + // Arrange. + // We need to be in the WP admin dashboard. + $this->set_is_admin( true ); + // Test as an admin user. + wp_set_current_user( 1 ); + + // Configure the request to be in sandbox mode. + $_GET['wcpay-connect'] = 'connect-from'; + $_REQUEST['_wpnonce'] = wp_create_nonce( 'wcpay-connect' ); + $_GET['progressive'] = 'true'; + $_GET['test_mode'] = 'true'; + $_GET['from'] = WC_Payments_Onboarding_Service::FROM_ONBOARDING_WIZARD; + + // The Jetpack connection is in working order. + $this->mock_jetpack_connection(); + + $this->mock_api_client + ->expects( $this->once() ) + ->method( 'get_onboarding_data' ) + ->willReturn( + [ + 'url' => false, + 'woopay_enabled_by_default' => true, + ] + ); + + $original_value = get_transient( WC_Payments_Account::WOOPAY_ENABLED_BY_DEFAULT_TRANSIENT ); + + // Act. + $this->wcpay_account->maybe_handle_onboarding(); + + // Assert. + $this->assertFalse( $original_value ); + $this->assertTrue( get_transient( WC_Payments_Account::WOOPAY_ENABLED_BY_DEFAULT_TRANSIENT ) ); + } + + public function test_ensure_woopay_not_enabled_by_default_for_existing_live_accounts() { + // Arrange. + // We need to be in the WP admin dashboard. + $this->set_is_admin( true ); + // Test as an admin user. + wp_set_current_user( 1 ); + + // Configure the request to be in sandbox mode. + $_GET['wcpay-connect'] = 'connect-from'; + $_REQUEST['_wpnonce'] = wp_create_nonce( 'wcpay-connect' ); + $_GET['progressive'] = 'true'; + $_GET['from'] = WC_Payments_Onboarding_Service::FROM_ONBOARDING_WIZARD; + + // The Jetpack connection is in working order. + $this->mock_jetpack_connection(); + + $this->mock_api_client + ->expects( $this->once() ) + ->method( 'get_onboarding_data' ) + ->willReturn( + [ + 'url' => false, + 'woopay_enabled_by_default' => true, + ] + ); + + // Act. + $this->wcpay_account->maybe_handle_onboarding(); + + // Assert. + $this->assertFalse( get_transient( WC_Payments_Account::WOOPAY_ENABLED_BY_DEFAULT_TRANSIENT ) ); + } + public function test_maybe_handle_onboarding_init_stripe_onboarding_existing_account() { // Arrange. // We need to be in the WP admin dashboard. From 009bc4416b6a3d74efe68e79b7ae2c5195d43531 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Fri, 13 Dec 2024 17:27:25 -0500 Subject: [PATCH 47/83] Enable ECE for Virtual Variable Subscriptions with Free Trials (#9917) --- changelog/as-fix-ece-variable-subs-free-trial | 4 ++++ client/express-checkout/index.js | 23 +++++++++++++++---- client/express-checkout/utils/index.ts | 1 + ...payments-express-checkout-ajax-handler.php | 1 + ...ayments-express-checkout-button-helper.php | 5 +--- 5 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 changelog/as-fix-ece-variable-subs-free-trial diff --git a/changelog/as-fix-ece-variable-subs-free-trial b/changelog/as-fix-ece-variable-subs-free-trial new file mode 100644 index 00000000000..64d67393c06 --- /dev/null +++ b/changelog/as-fix-ece-variable-subs-free-trial @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Enable ECE for Virtual Variable Subscriptions with Free Trials. diff --git a/client/express-checkout/index.js b/client/express-checkout/index.js index 0e2239946b6..447f0c81198 100644 --- a/client/express-checkout/index.js +++ b/client/express-checkout/index.js @@ -363,7 +363,7 @@ jQuery( ( $ ) => { } ); if ( getExpressCheckoutData( 'button_context' ) === 'product' ) { - wcpayECE.attachProductPageEventListeners( elements ); + wcpayECE.attachProductPageEventListeners( elements, eceButton ); } }, @@ -414,7 +414,7 @@ jQuery( ( $ ) => { return api.expressCheckoutECEGetSelectedProductData( data ); }, - attachProductPageEventListeners: ( elements ) => { + attachProductPageEventListeners: ( elements, eceButton ) => { // WooCommerce Deposits support. // Trigger the "woocommerce_variation_has_changed" event when the deposit option is changed. // Needs to be defined before the `woocommerce_variation_has_changed` event handler is set. @@ -437,6 +437,18 @@ jQuery( ( $ ) => { $.when( wcpayECE.getSelectedProductData() ) .then( ( response ) => { + // We do not support variable subscriptions with variations + // that require shipping and include a free trial. + if ( + getExpressCheckoutData( 'product' ) + .product_type === 'variable-subscription' && + response.needs_shipping && + response.has_free_trial + ) { + eceButton.destroy(); + return; + } + const isDeposits = wcpayECE.productHasDepositOption(); /** * If the customer aborted the express checkout, @@ -449,8 +461,11 @@ jQuery( ( $ ) => { ! wcpayECE.paymentAborted && getExpressCheckoutData( 'product' ) .needs_shipping === response.needs_shipping; - - if ( ! isDeposits && needsShipping ) { + if ( + ! isDeposits && + needsShipping && + ! ( eceButton._destroyed ?? false ) + ) { elements.update( { amount: response.total.amount, } ); diff --git a/client/express-checkout/utils/index.ts b/client/express-checkout/utils/index.ts index cfbc2b25b2b..7e6c4bf2d09 100644 --- a/client/express-checkout/utils/index.ts +++ b/client/express-checkout/utils/index.ts @@ -66,6 +66,7 @@ export interface WCPayExpressCheckoutParams { product: { needs_shipping: boolean; currency: string; + product_type: string; shippingOptions: { id: string; label: string; diff --git a/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php b/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php index bdf54ec01cf..d14460da71e 100644 --- a/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php +++ b/includes/express-checkout/class-wc-payments-express-checkout-ajax-handler.php @@ -333,6 +333,7 @@ public function ajax_get_selected_product_data() { $data['needs_shipping'] = wc_shipping_enabled() && 0 !== wc_get_shipping_method_count( true ) && $product->needs_shipping(); $data['currency'] = strtolower( get_woocommerce_currency() ); $data['country_code'] = substr( get_option( 'woocommerce_default_country' ), 0, 2 ); + $data['has_free_trial'] = class_exists( 'WC_Subscriptions_Product' ) ? WC_Subscriptions_Product::get_trial_length( $product ) > 0 : false; wp_send_json( $data ); } catch ( Exception $e ) { 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 26b72aa941c..672f2584c67 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 @@ -742,6 +742,7 @@ public function get_product_data() { $data['needs_shipping'] = ( wc_shipping_enabled() && 0 !== wc_get_shipping_method_count( true ) && $product->needs_shipping() ); $data['currency'] = strtolower( $currency ); $data['country_code'] = substr( get_option( 'woocommerce_default_country' ), 0, 2 ); + $data['product_type'] = $product->get_type(); return apply_filters( 'wcpay_payment_request_product_data', $data, $product ); } @@ -767,13 +768,9 @@ private function is_product_supported() { // Simple subscription that needs shipping with free trials is not supported. $is_free_trial_simple_subs = class_exists( 'WC_Subscriptions_Product' ) && $product->get_type() === 'subscription' && $product->needs_shipping() && WC_Subscriptions_Product::get_trial_length( $product ) > 0; - // Disable ECE for all variable subscriptions with free trials, as they are not currently supported. For now, ECE will be disabled for all such cases, and a solution will be addressed in a separate PR. - $is_free_trial_variable_sub = class_exists( 'WC_Subscriptions_Product' ) && $product->get_type() === 'variable-subscription' && WC_Subscriptions_Product::get_trial_length( $product ) > 0; - if ( ! in_array( $product->get_type(), $this->supported_product_types(), true ) || $is_free_trial_simple_subs - || $is_free_trial_variable_sub || ( class_exists( 'WC_Pre_Orders_Product' ) && WC_Pre_Orders_Product::product_is_charged_upon_release( $product ) ) // Pre Orders charge upon release not supported. || ( class_exists( 'WC_Composite_Products' ) && $product->is_type( 'composite' ) ) // Composite products are not supported on the product page. || ( class_exists( 'WC_Mix_and_Match' ) && $product->is_type( 'mix-and-match' ) ) // Mix and match products are not supported on the product page. From cb65e92cc9d3a263713a22998439e1cfad23d6ec Mon Sep 17 00:00:00 2001 From: Cvetan Cvetanov Date: Sun, 15 Dec 2024 21:15:46 +0200 Subject: [PATCH 48/83] Add support for utilizing NOX capabilities as URL parameters during account creation (#9947) Co-authored-by: oaratovskyi Co-authored-by: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com> Co-authored-by: Vlad Olaru --- ...s-capabilities-to-onboarding-as-get-params | 4 + client/connect-account-page/index.tsx | 1 + client/onboarding/utils.ts | 2 + includes/class-wc-payments-account.php | 44 +++++-- .../class-wc-payments-onboarding-service.php | 108 +++++++++++++++++- 5 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 changelog/update-pass-capabilities-to-onboarding-as-get-params diff --git a/changelog/update-pass-capabilities-to-onboarding-as-get-params b/changelog/update-pass-capabilities-to-onboarding-as-get-params new file mode 100644 index 00000000000..9104e7a8f99 --- /dev/null +++ b/changelog/update-pass-capabilities-to-onboarding-as-get-params @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add support for utilizing NOX capabilities as URL parameters during account creation. diff --git a/client/connect-account-page/index.tsx b/client/connect-account-page/index.tsx index 6502975d642..2b3f402abcb 100644 --- a/client/connect-account-page/index.tsx +++ b/client/connect-account-page/index.tsx @@ -215,6 +215,7 @@ const ConnectAccountPage: React.FC = () => { const customizedConnectUrl = addQueryArgs( connectUrl, { test_drive: 'true', + capabilities: urlParams.get( 'capabilities' ) || '', } ); const updateProgress = setInterval( updateLoaderProgress, 2500, 40, 5 ); diff --git a/client/onboarding/utils.ts b/client/onboarding/utils.ts index a95c3e298ef..306328f64f7 100644 --- a/client/onboarding/utils.ts +++ b/client/onboarding/utils.ts @@ -65,9 +65,11 @@ export const createAccountSession = async ( data: OnboardingFields, isPoEligible: boolean ): Promise< AccountKycSession > => { + const urlParams = new URLSearchParams( window.location.search ); return await apiFetch< AccountKycSession >( { path: addQueryArgs( `${ NAMESPACE }/onboarding/kyc/session`, { self_assessment: fromDotNotation( data ), + capabilities: urlParams.get( 'capabilities' ) || '', progressive: isPoEligible, } ), method: 'GET', diff --git a/includes/class-wc-payments-account.php b/includes/class-wc-payments-account.php index 097ef468710..dc5430d6f0f 100644 --- a/includes/class-wc-payments-account.php +++ b/includes/class-wc-payments-account.php @@ -1497,7 +1497,7 @@ public function maybe_handle_onboarding() { // If there is a working one, we can proceed with the Stripe account handling. try { $this->maybe_init_jetpack_connection( - // Carry over all the important GET params, so we have them after the Jetpack connection setup. + // Carry over all the important GET params, so we have them after the Jetpack connection setup. add_query_arg( [ 'promo' => ! empty( $incentive_id ) ? $incentive_id : false, @@ -1506,6 +1506,10 @@ public function maybe_handle_onboarding() { 'test_mode' => $should_onboard_in_test_mode ? 'true' : false, 'test_drive' => $create_test_drive_account ? 'true' : false, 'auto_start_test_drive_onboarding' => $auto_start_test_drive_onboarding ? 'true' : false, + // These are starting capabilities for the account. + // They are collected by the payment method step of the + // WC Payments settings page native onboarding experience. + 'capabilities' => rawurlencode( wp_json_encode( $this->onboarding_service->get_capabilities_from_request() ) ), 'from' => WC_Payments_Onboarding_Service::FROM_WPCOM_CONNECTION, 'source' => $onboarding_source, 'redirect_to_settings_page' => $redirect_to_settings_page ? 'true' : false, @@ -1534,13 +1538,19 @@ public function maybe_handle_onboarding() { && WC_Payments_Onboarding_Service::FROM_ONBOARDING_WIZARD !== $from && ! $this->is_stripe_connected() ) { + $additional_params = [ + 'source' => $onboarding_source, + ]; + + if ( $this->onboarding_service->get_capabilities_from_request() ) { + $additional_params['capabilities'] = rawurlencode( wp_json_encode( $this->onboarding_service->get_capabilities_from_request() ) ); + } + $this->redirect_service->redirect_to_onboarding_wizard( // When we redirect to the onboarding wizard, we carry over the `from`, if we have it. // This is because there is no interim step between the user clicking the connect link and the onboarding wizard. ! empty( $from ) ? $from : $next_step_from, - [ - 'source' => $onboarding_source, - ] + $additional_params ); return; } @@ -1573,11 +1583,15 @@ public function maybe_handle_onboarding() { null, $from, // Carry over `from` since we are doing a short-circuit. [ - 'promo' => ! empty( $incentive_id ) ? $incentive_id : false, - 'test_drive' => 'true', + 'promo' => ! empty( $incentive_id ) ? $incentive_id : false, + 'test_drive' => 'true', 'auto_start_test_drive_onboarding' => 'true', // This is critical. - 'test_mode' => $should_onboard_in_test_mode ? 'true' : false, - 'source' => $onboarding_source, + // These are starting capabilities for the account. + // They are collected by the payment method step of the + // WC Payments settings page native onboarding experience. + 'capabilities' => rawurlencode( wp_json_encode( $this->onboarding_service->get_capabilities_from_request() ) ), + 'test_mode' => $should_onboard_in_test_mode ? 'true' : false, + 'source' => $onboarding_source, 'redirect_to_settings_page' => $redirect_to_settings_page ? 'true' : false, ] ); @@ -1982,6 +1996,7 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne } $self_assessment_data = isset( $_GET['self_assessment'] ) ? wc_clean( wp_unslash( $_GET['self_assessment'] ) ) : []; + if ( 'test_drive' === $setup_mode ) { // If we get to the overview page, we want to show the success message. $return_url = add_query_arg( 'wcpay-sandbox-success', 'true', $return_url ); @@ -1996,7 +2011,14 @@ private function init_stripe_onboarding( string $setup_mode, string $wcpay_conne ]; $user_data = $this->onboarding_service->get_onboarding_user_data(); - $account_data = $this->onboarding_service->get_account_data( $setup_mode, $self_assessment_data ); + $account_data = $this->onboarding_service->get_account_data( + $setup_mode, + $self_assessment_data, + // These are starting capabilities for the account. + // They are collected by the payment method step of the + // WC Payments settings page native onboarding experience. + $this->onboarding_service->get_capabilities_from_request() + ); $onboarding_data = $this->payments_api_client->get_onboarding_data( 'live' === $setup_mode, @@ -2594,7 +2616,9 @@ public function get_lifetime_total_payment_volume(): int { } /** - * Extract the test drive settings from the account data that we want to store for the live account. + * Extract the useful test drive settings from the account data. + * + * We will use this data to migrate the test drive settings when onboarding the live account. * ATM we only store the enabled payment methods. * * @return array The test drive settings for the live account. diff --git a/includes/class-wc-payments-onboarding-service.php b/includes/class-wc-payments-onboarding-service.php index ce558ac2faf..504ac3bd5e4 100644 --- a/includes/class-wc-payments-onboarding-service.php +++ b/includes/class-wc-payments-onboarding-service.php @@ -180,6 +180,69 @@ function () use ( $country_code, $locale ) { ); } + /** + * Get the onboarding capabilities from the request. + * + * The capabilities are expected to be passed as an array of capabilities keyed by the capability ID and + * with boolean values. If the value is true, the capability is requested when the account is created. + * + * @return array The standardized capabilities that were passed in the request. + * Empty array if no capabilities were passed or none were valid. + */ + public function get_capabilities_from_request(): array { + $capabilities = []; + + if ( empty( $_REQUEST['capabilities'] ) ) { // phpcs:disable WordPress.Security.NonceVerification.Recommended + return $capabilities; + } + + // Try to extract the capabilities. + // They might be already decoded or not, so we need to handle both cases. + // We expect them to be an array. + // We disable the warning because we have our own sanitization and validation. + // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized + $capabilities = wp_unslash( $_REQUEST['capabilities'] ); + if ( ! is_array( $capabilities ) ) { + $capabilities = json_decode( $capabilities, true ) ?? []; + } + + if ( empty( $capabilities ) ) { + return []; + } + + // Sanitize and validate. + $capabilities = array_combine( + array_map( + function ( $key ) { + // Keep numeric keys as integers so we can remove them later. + if ( is_numeric( $key ) ) { + return intval( $key ); + } + + return sanitize_text_field( $key ); + }, + array_keys( $capabilities ) + ), + array_map( + function ( $value ) { + return filter_var( $value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + }, + $capabilities + ) + ); + + // Filter out any invalid entries. + $capabilities = array_filter( + $capabilities, + function ( $value, $key ) { + return is_string( $key ) && is_bool( $value ); + }, + ARRAY_FILTER_USE_BOTH + ); + + return $capabilities; + } + /** * Retrieve the embedded KYC session and handle initial account creation (if necessary). * @@ -207,15 +270,19 @@ public function create_embedded_kyc_session( array $self_assessment_data, bool $ 'site_locale' => get_locale(), ]; $user_data = $this->get_onboarding_user_data(); - $account_data = $this->get_account_data( $setup_mode, $self_assessment_data ); + $account_data = $this->get_account_data( + $setup_mode, + $self_assessment_data, + $this->get_capabilities_from_request() + ); $actioned_notes = self::get_actioned_notes(); try { $account_session = $this->payments_api_client->initialize_onboarding_embedded_kyc( 'live' === $setup_mode, $site_data, - array_filter( $user_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. - array_filter( $account_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. + WC_Payments_Utils::array_filter_recursive( $user_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. + WC_Payments_Utils::array_filter_recursive( $account_data ), // nosemgrep: audit.php.lang.misc.array-filter-no-callback -- output of array_filter is escaped. $actioned_notes, $progressive ); @@ -365,12 +432,15 @@ public function add_admin_body_classes( string $classes = '' ): string { /** * Get account data for onboarding from self assessment data. * - * @param string $setup_mode Setup mode. + * @param string $setup_mode Setup mode. * @param array $self_assessment_data Self assessment data. + * @param array $capabilities Optional. List keyed by capabilities IDs (payment methods) with boolean values. + * If the value is true, the capability is requested when the account is created. + * If the value is false, the capability is not requested when the account is created. * * @return array Account data. */ - public function get_account_data( string $setup_mode, array $self_assessment_data ): array { + public function get_account_data( string $setup_mode, array $self_assessment_data, array $capabilities = [] ): array { $home_url = get_home_url(); // If the site is running on localhost, use a bogus URL. This is to avoid Stripe's errors. // wp_http_validate_url does not check that, unfortunately. @@ -387,6 +457,33 @@ public function get_account_data( string $setup_mode, array $self_assessment_dat 'business_name' => get_bloginfo( 'name' ), ]; + foreach ( $capabilities as $capability => $should_request ) { + // Remove the `_payments` suffix from the capability, if present. + if ( strpos( $capability, '_payments' ) === strlen( $capability ) - 9 ) { + $capability = str_replace( '_payments', '', $capability ); + } + + // Skip the special 'apple_google' because it is not a payment method. + // Skip the 'woopay' because it is automatically handled by the API. + if ( 'apple_google' === $capability || 'woopay' === $capability ) { + continue; + } + + if ( 'card' === $capability ) { + // Card is always requested. + $account_data['capabilities']['card_payments'] = [ 'requested' => 'true' ]; + // When requesting card, we also need to request transfers. + // The platform should handle this automatically, but it is best to be thorough. + $account_data['capabilities']['transfers'] = [ 'requested' => 'true' ]; + continue; + } + + // We only request, not unrequest capabilities. + if ( $should_request ) { + $account_data['capabilities'][ $capability . '_payments' ] = [ 'requested' => 'true' ]; + } + } + if ( ! empty( $self_assessment_data ) ) { $business_type = $self_assessment_data['business_type'] ?? null; $account_data = WC_Payments_Utils::array_merge_recursive_distinct( @@ -436,6 +533,7 @@ public function get_account_data( string $setup_mode, array $self_assessment_dat ] ); } + return $account_data; } From db8947f0ca24fb6b91c2f45d5f3d2864444614a9 Mon Sep 17 00:00:00 2001 From: Eric Jinks <3147296+Jinksi@users.noreply.github.com> Date: Mon, 16 Dec 2024 09:57:57 +1000 Subject: [PATCH 49/83] Remove temporary transaction table search field CSS that was used for Payment Activity Card report filtering (#9881) Co-authored-by: Jessy Co-authored-by: Jessy Pappachan <32092402+jessy-p@users.noreply.github.com> Co-authored-by: Shendy <73803630+shendy-a8c@users.noreply.github.com> --- ...ry-payment-activity-transaction-search-css | 4 ++ client/transactions/list/style.scss | 52 ------------------- 2 files changed, 4 insertions(+), 52 deletions(-) create mode 100644 changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css diff --git a/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css b/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css new file mode 100644 index 00000000000..3841ea6164e --- /dev/null +++ b/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. diff --git a/client/transactions/list/style.scss b/client/transactions/list/style.scss index 45d8494de81..c552ef65af4 100644 --- a/client/transactions/list/style.scss +++ b/client/transactions/list/style.scss @@ -153,56 +153,4 @@ $gap-small: 12px; height: auto; } } - - .components-card__header { - // These styles improve the overflow behaviour of the Search component within the TableCard, when many tags are selected. Used for transaction list views. See PR #8996 - .woocommerce-search.woocommerce-select-control - .woocommerce-select-control__listbox { - position: relative; - top: 5px; - } - .woocommerce-table__actions { - justify-content: space-between; - - & > div { - width: 85%; - margin-right: 0; - } - - button.woocommerce-table__download-button { - @include breakpoint( '<1040px' ) { - .woocommerce-table__download-button__label { - display: none; - } - } - } - - .woocommerce-select-control.is-focused - .woocommerce-select-control__control { - flex-wrap: wrap; - - .woocommerce-select-control__tags { - white-space: wrap; - } - } - .woocommerce-select-control__tags { - overflow-x: auto; - white-space: nowrap; - scrollbar-width: none; - margin-right: 25px; - } - - .woocommerce-select-control.is-focused - .components-base-control - .components-base-control__field { - flex-basis: 45%; - } - - @include breakpoint( '<960px' ) { - .woocommerce-search { - margin: 0; - } - } - } - } } From 7639bc4ab6b99914894f8f69d99bba0fd463fa51 Mon Sep 17 00:00:00 2001 From: Daniel Guerra <15204776+danielmx-dev@users.noreply.github.com> Date: Tue, 17 Dec 2024 00:13:56 -0600 Subject: [PATCH 50/83] Checkout: Fix error when wc_address_i18n_params does not have data for a given country (#9974) --- ...low-addresses-from-woo-supported-countries | 4 + client/checkout/utils/test/upe.test.js | 138 +++++++++++++++++- client/checkout/utils/upe.js | 4 +- 3 files changed, 137 insertions(+), 9 deletions(-) create mode 100644 changelog/fix-allow-addresses-from-woo-supported-countries diff --git a/changelog/fix-allow-addresses-from-woo-supported-countries b/changelog/fix-allow-addresses-from-woo-supported-countries new file mode 100644 index 00000000000..626fd1ce34f --- /dev/null +++ b/changelog/fix-allow-addresses-from-woo-supported-countries @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Checkout: Fix error when wc_address_i18n_params does not have data for a given country diff --git a/client/checkout/utils/test/upe.test.js b/client/checkout/utils/test/upe.test.js index 7357840b51a..91ad592d5cf 100644 --- a/client/checkout/utils/test/upe.test.js +++ b/client/checkout/utils/test/upe.test.js @@ -12,6 +12,7 @@ import { isUsingSavedPaymentMethod, dispatchChangeEventFor, togglePaymentMethodForCountry, + isBillingInformationMissing, } from '../upe'; import { getPaymentMethodsConstants } from '../../constants'; @@ -22,11 +23,134 @@ jest.mock( 'wcpay/utils/checkout' ); jest.mock( '../../constants', () => { return { + ...jest.requireActual( '../../constants' ), getPaymentMethodsConstants: jest.fn(), }; } ); +function buildForm( fields ) { + const form = document.createElement( 'form' ); + fields.forEach( ( field ) => { + const input = document.createElement( 'input' ); + input.id = field.id; + input.value = field.value; + form.appendChild( input ); + } ); + return form; +} + describe( 'UPE checkout utils', () => { + describe( 'isBillingInformationMissing', () => { + beforeAll( () => { + window.wc_address_i18n_params = { + locale: { + US: {}, + HK: { + postcode: { required: false }, + }, + default: { + address_1: { required: true }, + postcode: { required: true }, + }, + }, + }; + } ); + + beforeEach( () => { + getUPEConfig.mockImplementation( ( argument ) => { + if ( argument === 'enabledBillingFields' ) { + return { + billing_first_name: { + required: true, + }, + billing_last_name: { + required: true, + }, + billing_company: { + required: false, + }, + billing_country: { + required: true, + }, + billing_address_1: { + required: true, + }, + billing_address_2: { + required: false, + }, + billing_city: { + required: true, + }, + billing_state: { + required: true, + }, + billing_postcode: { + required: true, + }, + billing_phone: { + required: true, + }, + billing_email: { + required: true, + }, + }; + } + } ); + } ); + + it( 'should return false when the billing information is not missing', () => { + const form = buildForm( [ + { id: 'billing_first_name', value: 'Test' }, + { id: 'billing_last_name', value: 'User' }, + { id: 'billing_email', value: 'test@example.com' }, + { id: 'billing_country', value: 'US' }, + { id: 'billing_address_1', value: '123 Main St' }, + { id: 'billing_city', value: 'Anytown' }, + { id: 'billing_postcode', value: '12345' }, + ] ); + expect( isBillingInformationMissing( form ) ).toBe( false ); + } ); + + it( 'should return true when the billing information is missing', () => { + const form = buildForm( [ + { id: 'billing_first_name', value: 'Test' }, + { id: 'billing_last_name', value: 'User' }, + { id: 'billing_email', value: 'test@example.com' }, + { id: 'billing_country', value: 'US' }, + { id: 'billing_address_1', value: '123 Main St' }, + { id: 'billing_city', value: 'Anytown' }, + { id: 'billing_postcode', value: '' }, + ] ); + expect( isBillingInformationMissing( form ) ).toBe( true ); + } ); + + it( 'should use the defaults when there is no specific locale data for a country', () => { + const form = buildForm( [ + { id: 'billing_first_name', value: 'Test' }, + { id: 'billing_last_name', value: 'User' }, + { id: 'billing_email', value: 'test@example.com' }, + { id: 'billing_country', value: 'MX' }, + { id: 'billing_address_1', value: '123 Main St' }, + { id: 'billing_city', value: 'Anytown' }, + { id: 'billing_postcode', value: '' }, + ] ); + expect( isBillingInformationMissing( form ) ).toBe( true ); + } ); + + it( 'should return false when the locale data for a country has no required fields', () => { + const form = buildForm( [ + { id: 'billing_first_name', value: 'Test' }, + { id: 'billing_last_name', value: 'User' }, + { id: 'billing_email', value: 'test@example.com' }, + { id: 'billing_country', value: 'HK' }, + { id: 'billing_address_1', value: '123 Main St' }, + { id: 'billing_city', value: 'Anytown' }, + { id: 'billing_postcode', value: '' }, + ] ); + expect( isBillingInformationMissing( form ) ).toBe( true ); + } ); + } ); + describe( 'getSelectedUPEGatewayPaymentMethod', () => { let container; @@ -54,7 +178,7 @@ describe( 'UPE checkout utils', () => { } ); test( 'Selected UPE Payment Method is card', () => { - container.innerHTML = ` { test( 'Selected UPE Payment Method is bancontact', () => { container.innerHTML = ` - `; diff --git a/client/checkout/utils/upe.js b/client/checkout/utils/upe.js index af8e0427c04..c8201ff1ba1 100644 --- a/client/checkout/utils/upe.js +++ b/client/checkout/utils/upe.js @@ -449,8 +449,8 @@ export const isBillingInformationMissing = ( form ) => { if ( country && locale && fieldName !== 'billing_email' ) { const key = fieldName.replace( 'billing_', '' ); isRequired = - locale[ country ][ key ]?.required ?? - locale.default[ key ]?.required; + locale[ country ]?.[ key ]?.required ?? + locale.default?.[ key ]?.required; } const hasValue = field?.value; From 16603e308255e0fdf7f82f8bc64172d0ef583ead Mon Sep 17 00:00:00 2001 From: botwoo Date: Tue, 17 Dec 2024 08:19:29 +0000 Subject: [PATCH 51/83] Amend changelog entries for release 8.6.1 --- changelog.txt | 41 ++++++++++- changelog/8969-fallback-to-card-payment-type | 5 -- .../add-2253-clickwrap-terms-and-conditions | 4 -- .../add-6924-migrate-test-drive-capabilities | 4 -- changelog/add-9690-recommended-pm | 4 -- changelog/add-jetpack-config-callback | 4 -- ...imit-woopay-themeing-to-shortcode-checkout | 5 -- .../add-pass-footer-header-styles-to-woopay | 5 -- changelog/as-fix-ece-variable-subs | 4 -- changelog/as-fix-ece-variable-subs-free-trial | 4 -- changelog/as-hk-address | 4 -- .../chore-prb-references-in-ece-docs-and-logs | 4 -- ...e-remove-ece-error-assignment-on-loaderror | 5 -- ...emove-tokenized-payment-request-references | 5 -- .../compat-9727-avoid-early-translations | 4 -- changelog/dev-qit-auth-fix-take-2 | 4 -- ...nized-ece-product-page-base-implementation | 5 -- ...ndle-error-on-refund-during-manual-capture | 4 -- .../fix-7230-payments-details-mobile-view | 4 -- ...ix-9421-auto-enable-woopay-in-sandbox-mode | 4 -- changelog/fix-9612-inquiry-order-note | 4 -- ...ry-payment-activity-transaction-search-css | 4 -- .../fix-9787-woopay-enable-state-settings | 4 -- ...-9806-ECE-subscription-checkout-signed-out | 4 -- ...x-9830-browser-error-on-dispute-submission | 4 -- changelog/fix-9889-log-level | 4 -- changelog/fix-add-payment-method-check | 5 -- ...low-addresses-from-woo-supported-countries | 4 -- .../fix-change-woopay-theming-settings-copy | 4 -- .../fix-php-8-compatibility-errors-warnings | 5 -- .../fix-rounding-error-with-deposit-products | 4 -- changelog/fix-skip-ssl-requirement-env-setup | 4 -- changelog/fix-stripe-link-button | 4 -- .../fix-tokenized-cart-error-notice-json | 5 -- changelog/fix-upe-country-selection | 4 -- changelog/fix-upe-theme-block | 4 -- changelog/fix-use-effect-console-warning | 5 -- changelog/fix-use-type-is-in-filter | 4 -- changelog/frosso-patch-1 | 4 -- changelog/mobile-tpv-tracking-channel | 4 -- changelog/replace-from-url-query | 4 -- changelog/scope-payment-elements-selectors | 4 -- changelog/test-instructions-item-color | 4 -- .../update-1-5316-rename-bank-reference-id | 4 -- changelog/update-7900-payout-notice | 4 -- changelog/update-9910-transaction-id-label | 5 -- .../update-9916-go-live-modal-and-notice | 4 -- changelog/update-confirmation-modal-nox | 4 -- changelog/update-jetpack-onboarding-flow | 4 -- ...s-capabilities-to-onboarding-as-get-params | 4 -- changelog/update-server-container-name | 5 -- changelog/update-to-standalone-jt | 4 -- readme.txt | 72 +++++++++++++++++++ 53 files changed, 112 insertions(+), 217 deletions(-) delete mode 100644 changelog/8969-fallback-to-card-payment-type delete mode 100644 changelog/add-2253-clickwrap-terms-and-conditions delete mode 100644 changelog/add-6924-migrate-test-drive-capabilities delete mode 100644 changelog/add-9690-recommended-pm delete mode 100644 changelog/add-jetpack-config-callback delete mode 100644 changelog/add-limit-woopay-themeing-to-shortcode-checkout delete mode 100644 changelog/add-pass-footer-header-styles-to-woopay delete mode 100644 changelog/as-fix-ece-variable-subs delete mode 100644 changelog/as-fix-ece-variable-subs-free-trial delete mode 100644 changelog/as-hk-address delete mode 100644 changelog/chore-prb-references-in-ece-docs-and-logs delete mode 100644 changelog/chore-remove-ece-error-assignment-on-loaderror delete mode 100644 changelog/chore-remove-tokenized-payment-request-references delete mode 100644 changelog/compat-9727-avoid-early-translations delete mode 100644 changelog/dev-qit-auth-fix-take-2 delete mode 100644 changelog/feat-tokenized-ece-product-page-base-implementation delete mode 100644 changelog/fix-5671-handle-error-on-refund-during-manual-capture delete mode 100644 changelog/fix-7230-payments-details-mobile-view delete mode 100644 changelog/fix-9421-auto-enable-woopay-in-sandbox-mode delete mode 100644 changelog/fix-9612-inquiry-order-note delete mode 100644 changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css delete mode 100644 changelog/fix-9787-woopay-enable-state-settings delete mode 100644 changelog/fix-9806-ECE-subscription-checkout-signed-out delete mode 100644 changelog/fix-9830-browser-error-on-dispute-submission delete mode 100644 changelog/fix-9889-log-level delete mode 100644 changelog/fix-add-payment-method-check delete mode 100644 changelog/fix-allow-addresses-from-woo-supported-countries delete mode 100644 changelog/fix-change-woopay-theming-settings-copy delete mode 100644 changelog/fix-php-8-compatibility-errors-warnings delete mode 100644 changelog/fix-rounding-error-with-deposit-products delete mode 100644 changelog/fix-skip-ssl-requirement-env-setup delete mode 100644 changelog/fix-stripe-link-button delete mode 100644 changelog/fix-tokenized-cart-error-notice-json delete mode 100644 changelog/fix-upe-country-selection delete mode 100644 changelog/fix-upe-theme-block delete mode 100644 changelog/fix-use-effect-console-warning delete mode 100644 changelog/fix-use-type-is-in-filter delete mode 100644 changelog/frosso-patch-1 delete mode 100644 changelog/mobile-tpv-tracking-channel delete mode 100644 changelog/replace-from-url-query delete mode 100644 changelog/scope-payment-elements-selectors delete mode 100644 changelog/test-instructions-item-color delete mode 100644 changelog/update-1-5316-rename-bank-reference-id delete mode 100644 changelog/update-7900-payout-notice delete mode 100644 changelog/update-9910-transaction-id-label delete mode 100644 changelog/update-9916-go-live-modal-and-notice delete mode 100644 changelog/update-confirmation-modal-nox delete mode 100644 changelog/update-jetpack-onboarding-flow delete mode 100644 changelog/update-pass-capabilities-to-onboarding-as-get-params delete mode 100644 changelog/update-server-container-name delete mode 100644 changelog/update-to-standalone-jt diff --git a/changelog.txt b/changelog.txt index 969e9401b6c..7661027758a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,37 +1,76 @@ *** WooPayments Changelog *** -= 8.6.0 - 2024-12-04 = += 8.6.1 - 2024-12-17 = * Add - Add Bank reference key column in Payout reports. This will help reconcile WooPayments Payouts with bank statements. +* Add - Clickwrap terms and conditions support on WooPay * Add - Display credit card brand icons on order received page. +* Add - Implement gateway method to retrieve recommended payment method. +* Add - Migrate active capabilities from test-drive account when switching to live account. +* Fix - Added conditional use of Jetpack Config callback to avoid i18n notices. * Fix - Add support to load stripe js asynchronously when it is not immediately available in the global scope. * Fix - Add the missing "Download" column heading label and toggle menu option to the Payments → Documents list view table. +* Fix - Browser error no longer shows after dispute evidence submission +* Fix - Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. +* Fix - Checkout: Fix error when wc_address_i18n_params does not have data for a given country +* Fix - Consider WooPay eligibility when retrieving WooPay enable state in the settings. +* Fix - Enable ECE for Virtual Variable Subscriptions with Free Trials. * Fix - Ensure ECE button load events are triggered for multiple buttons on the same page. * Fix - Ensure ECE is displayed correctly taking into account the tax settings. +* Fix - Ensure ECE login confirmation dialog is shown on Blocks. +* Fix - Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. +* Fix - Errors were incorrectly marked as info in logs. * Fix - Evidence submission is no longer available for Klarna inquiries as this is not supported by Stripe / Klarna. * Fix - fix: express checkout to use its own css files. * Fix - fix: missing ece is_product_page checks +* Fix - fix: undefined $cart_contains_subscription * Fix - Fix ECE Tracks events not triggering when WooPay is disabled. +* Fix - Fixed Affirm using black logo on dark themes +* Fix - Fixed an issue where order metadata was not updated when capturing an order in the processing state. +* Fix - Fixed UPE country detection in Checkout for non-logged in users +* Fix - Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. +* Fix - Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" +* Fix - Fix styling of transaction details page in mobile view. * Fix - Fix WooPay component spacing. * Fix - Fix WooPay trial subscriptions purchases. * Fix - Make sure the status of manual capture enablement is fetched from the right place. +* Fix - Normalize HK addresses for ECE +* Fix - Order notes for inquiries have clearer content. * Fix - Prevent express checkout from being used if cart total becomes zero after coupon usage. +* Fix - Remove translations during initialization, preventing unnecessary warnings. * Fix - Resolved issue with terminal payments in the payment intent failed webhook processing. +* Fix - Restrict Stripe Link to credit card payment method and improve cleanup. * Fix - Set the support phone field as mandatory in the settings page. +* Fix - Skip mysqlcheck SSL Requirement during E2E environment setup +* Fix - Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. * Fix - Update Link logo alignment issue when WooPay is enabled and a specific version of Gutenberg is enabled. * Fix - Use paragraph selector instead of label for pmme appearance * Fix - Validate required billing fields using data from objects instead of checking the labels. +* Update - Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. +* Update - Adjust the go-live modal to match the latest design. * Update - Avoid getting the appearance for pay for order page with the wrong appearance key. +* Update - Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. +* Update - chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. * Update - chore: rename wrapper from payment-request to express-checkout +* Update - Ensure more robust selectors scoping to improve theme compatibility. * Update - feat: add `wcpay_checkout_use_plain_method_label` filter to allow themes or merchants to force the "plain" WooPayments label on shortcode checkout. +* Update - Make test instructions copy icon use the same color as the text next to it * Update - refactor: express checkout initialization page location checks * Update - refactor: express checkout utility for button UI interactions +* Update - Remove payout timing notice and update the help tooltil on Payments Overview page. +* Update - Update confirmation modal after onbarding +* Update - Update Jetpack onboarding flow +* Update - WooPay theming copy in the settings page +* Dev - Add support for utilizing NOX capabilities as URL parameters during account creation. * Dev - Allow redirect to the settings page from WCPay connect * Dev - chore: removed old PRB implementation for ApplePay/GooglePay in favor of the ECE implementation; cleaned up ECE feature flag; * Dev - Disable visual regression testing from Playwright until a more reliable approach is defined. * Dev - Ensure proper return types in the webhook processing service. * Dev - fix: E_DEPRECATED on BNPL empty PMME +* Dev - Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. * Dev - Fix return types +* Dev - Refine verification for disabling ECE on subscriptions that require shipping. * Dev - Update snapshots for E2E Playwright screenshots +* Dev - Update the tunelling setup. = 8.5.1 - 2024-11-25 = * Fix - fix: remove "test mode" badge from shortcode checkout. diff --git a/changelog/8969-fallback-to-card-payment-type b/changelog/8969-fallback-to-card-payment-type deleted file mode 100644 index ee66dbfa7e7..00000000000 --- a/changelog/8969-fallback-to-card-payment-type +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: update -Comment: Small change to payment method types fallback scenario. - - diff --git a/changelog/add-2253-clickwrap-terms-and-conditions b/changelog/add-2253-clickwrap-terms-and-conditions deleted file mode 100644 index ac0a4ece4b7..00000000000 --- a/changelog/add-2253-clickwrap-terms-and-conditions +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Clickwrap terms and conditions support on WooPay diff --git a/changelog/add-6924-migrate-test-drive-capabilities b/changelog/add-6924-migrate-test-drive-capabilities deleted file mode 100644 index 7b280af4d92..00000000000 --- a/changelog/add-6924-migrate-test-drive-capabilities +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Migrate active capabilities from test-drive account when switching to live account. diff --git a/changelog/add-9690-recommended-pm b/changelog/add-9690-recommended-pm deleted file mode 100644 index 2d615350daa..00000000000 --- a/changelog/add-9690-recommended-pm +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Implement gateway method to retrieve recommended payment method. diff --git a/changelog/add-jetpack-config-callback b/changelog/add-jetpack-config-callback deleted file mode 100644 index 64b1a2abb1b..00000000000 --- a/changelog/add-jetpack-config-callback +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Added conditional use of Jetpack Config callback to avoid i18n notices. diff --git a/changelog/add-limit-woopay-themeing-to-shortcode-checkout b/changelog/add-limit-woopay-themeing-to-shortcode-checkout deleted file mode 100644 index 4c089593b1f..00000000000 --- a/changelog/add-limit-woopay-themeing-to-shortcode-checkout +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: add -Comment: Updates the availability criteria of WooPay Global theme-ing feature. This feature is unreleased and behind a feature flag. - - diff --git a/changelog/add-pass-footer-header-styles-to-woopay b/changelog/add-pass-footer-header-styles-to-woopay deleted file mode 100644 index ab6375db250..00000000000 --- a/changelog/add-pass-footer-header-styles-to-woopay +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: add -Comment: Impovements to WooPay themeing, which is not yet released to the public. - - diff --git a/changelog/as-fix-ece-variable-subs b/changelog/as-fix-ece-variable-subs deleted file mode 100644 index 236497bcab9..00000000000 --- a/changelog/as-fix-ece-variable-subs +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Refine verification for disabling ECE on subscriptions that require shipping. diff --git a/changelog/as-fix-ece-variable-subs-free-trial b/changelog/as-fix-ece-variable-subs-free-trial deleted file mode 100644 index 64d67393c06..00000000000 --- a/changelog/as-fix-ece-variable-subs-free-trial +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Enable ECE for Virtual Variable Subscriptions with Free Trials. diff --git a/changelog/as-hk-address b/changelog/as-hk-address deleted file mode 100644 index d58ddb9ffd9..00000000000 --- a/changelog/as-hk-address +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Normalize HK addresses for ECE diff --git a/changelog/chore-prb-references-in-ece-docs-and-logs b/changelog/chore-prb-references-in-ece-docs-and-logs deleted file mode 100644 index 887525ff7bc..00000000000 --- a/changelog/chore-prb-references-in-ece-docs-and-logs +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. diff --git a/changelog/chore-remove-ece-error-assignment-on-loaderror b/changelog/chore-remove-ece-error-assignment-on-loaderror deleted file mode 100644 index cce991d09ba..00000000000 --- a/changelog/chore-remove-ece-error-assignment-on-loaderror +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: update -Comment: chore: remove ECE error assignment on loaderror - - diff --git a/changelog/chore-remove-tokenized-payment-request-references b/changelog/chore-remove-tokenized-payment-request-references deleted file mode 100644 index 56dc3b0a0cc..00000000000 --- a/changelog/chore-remove-tokenized-payment-request-references +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: chore: remove tokeinzed payment request code - - diff --git a/changelog/compat-9727-avoid-early-translations b/changelog/compat-9727-avoid-early-translations deleted file mode 100644 index 51432b8cd10..00000000000 --- a/changelog/compat-9727-avoid-early-translations +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Remove translations during initialization, preventing unnecessary warnings. diff --git a/changelog/dev-qit-auth-fix-take-2 b/changelog/dev-qit-auth-fix-take-2 deleted file mode 100644 index 67ec99abd79..00000000000 --- a/changelog/dev-qit-auth-fix-take-2 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. diff --git a/changelog/feat-tokenized-ece-product-page-base-implementation b/changelog/feat-tokenized-ece-product-page-base-implementation deleted file mode 100644 index e0f342c1623..00000000000 --- a/changelog/feat-tokenized-ece-product-page-base-implementation +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: update -Comment: feat: tokenized ECE product page base implementation - - diff --git a/changelog/fix-5671-handle-error-on-refund-during-manual-capture b/changelog/fix-5671-handle-error-on-refund-during-manual-capture deleted file mode 100644 index 016c68f13aa..00000000000 --- a/changelog/fix-5671-handle-error-on-refund-during-manual-capture +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fixed an issue where order metadata was not updated when capturing an order in the processing state. diff --git a/changelog/fix-7230-payments-details-mobile-view b/changelog/fix-7230-payments-details-mobile-view deleted file mode 100644 index 93e179a44ca..00000000000 --- a/changelog/fix-7230-payments-details-mobile-view +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix styling of transaction details page in mobile view. diff --git a/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode b/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode deleted file mode 100644 index 30ec0c7fed5..00000000000 --- a/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. diff --git a/changelog/fix-9612-inquiry-order-note b/changelog/fix-9612-inquiry-order-note deleted file mode 100644 index 3fce0a23430..00000000000 --- a/changelog/fix-9612-inquiry-order-note +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Order notes for inquiries have clearer content. diff --git a/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css b/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css deleted file mode 100644 index 3841ea6164e..00000000000 --- a/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. diff --git a/changelog/fix-9787-woopay-enable-state-settings b/changelog/fix-9787-woopay-enable-state-settings deleted file mode 100644 index cee183680df..00000000000 --- a/changelog/fix-9787-woopay-enable-state-settings +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Consider WooPay eligibility when retrieving WooPay enable state in the settings. diff --git a/changelog/fix-9806-ECE-subscription-checkout-signed-out b/changelog/fix-9806-ECE-subscription-checkout-signed-out deleted file mode 100644 index fa25afd1f10..00000000000 --- a/changelog/fix-9806-ECE-subscription-checkout-signed-out +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ensure ECE login confirmation dialog is shown on Blocks. diff --git a/changelog/fix-9830-browser-error-on-dispute-submission b/changelog/fix-9830-browser-error-on-dispute-submission deleted file mode 100644 index 918ad744351..00000000000 --- a/changelog/fix-9830-browser-error-on-dispute-submission +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Browser error no longer shows after dispute evidence submission diff --git a/changelog/fix-9889-log-level b/changelog/fix-9889-log-level deleted file mode 100644 index d2f54e24c1a..00000000000 --- a/changelog/fix-9889-log-level +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Errors were incorrectly marked as info in logs. diff --git a/changelog/fix-add-payment-method-check b/changelog/fix-add-payment-method-check deleted file mode 100644 index 4ffc9e6342f..00000000000 --- a/changelog/fix-add-payment-method-check +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Added a check for the gateway id before comparing it - - diff --git a/changelog/fix-allow-addresses-from-woo-supported-countries b/changelog/fix-allow-addresses-from-woo-supported-countries deleted file mode 100644 index 626fd1ce34f..00000000000 --- a/changelog/fix-allow-addresses-from-woo-supported-countries +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Checkout: Fix error when wc_address_i18n_params does not have data for a given country diff --git a/changelog/fix-change-woopay-theming-settings-copy b/changelog/fix-change-woopay-theming-settings-copy deleted file mode 100644 index fa73b3672f8..00000000000 --- a/changelog/fix-change-woopay-theming-settings-copy +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -WooPay theming copy in the settings page diff --git a/changelog/fix-php-8-compatibility-errors-warnings b/changelog/fix-php-8-compatibility-errors-warnings deleted file mode 100644 index 9c393f71654..00000000000 --- a/changelog/fix-php-8-compatibility-errors-warnings +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: These changes fix some PHP compatibility errors that don't impact WooPayments behaviour. - - diff --git a/changelog/fix-rounding-error-with-deposit-products b/changelog/fix-rounding-error-with-deposit-products deleted file mode 100644 index d42215e3919..00000000000 --- a/changelog/fix-rounding-error-with-deposit-products +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. diff --git a/changelog/fix-skip-ssl-requirement-env-setup b/changelog/fix-skip-ssl-requirement-env-setup deleted file mode 100644 index 691f98adbfa..00000000000 --- a/changelog/fix-skip-ssl-requirement-env-setup +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Skip mysqlcheck SSL Requirement during E2E environment setup diff --git a/changelog/fix-stripe-link-button b/changelog/fix-stripe-link-button deleted file mode 100644 index d8acf0626f1..00000000000 --- a/changelog/fix-stripe-link-button +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Restrict Stripe Link to credit card payment method and improve cleanup. diff --git a/changelog/fix-tokenized-cart-error-notice-json b/changelog/fix-tokenized-cart-error-notice-json deleted file mode 100644 index c132e0f7eeb..00000000000 --- a/changelog/fix-tokenized-cart-error-notice-json +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: fix: tokenized cart error notice json - - diff --git a/changelog/fix-upe-country-selection b/changelog/fix-upe-country-selection deleted file mode 100644 index 478ffa1cfcd..00000000000 --- a/changelog/fix-upe-country-selection +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fixed UPE country detection in Checkout for non-logged in users diff --git a/changelog/fix-upe-theme-block b/changelog/fix-upe-theme-block deleted file mode 100644 index 6afa59f04d3..00000000000 --- a/changelog/fix-upe-theme-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fixed Affirm using black logo on dark themes diff --git a/changelog/fix-use-effect-console-warning b/changelog/fix-use-effect-console-warning deleted file mode 100644 index 45219e7b39a..00000000000 --- a/changelog/fix-use-effect-console-warning +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: fix: console warning on plugins page - - diff --git a/changelog/fix-use-type-is-in-filter b/changelog/fix-use-type-is-in-filter deleted file mode 100644 index 3639b203c36..00000000000 --- a/changelog/fix-use-type-is-in-filter +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. diff --git a/changelog/frosso-patch-1 b/changelog/frosso-patch-1 deleted file mode 100644 index e3812625698..00000000000 --- a/changelog/frosso-patch-1 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -fix: undefined $cart_contains_subscription diff --git a/changelog/mobile-tpv-tracking-channel b/changelog/mobile-tpv-tracking-channel deleted file mode 100644 index a7b990214df..00000000000 --- a/changelog/mobile-tpv-tracking-channel +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. diff --git a/changelog/replace-from-url-query b/changelog/replace-from-url-query deleted file mode 100644 index 58688e1c42f..00000000000 --- a/changelog/replace-from-url-query +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" diff --git a/changelog/scope-payment-elements-selectors b/changelog/scope-payment-elements-selectors deleted file mode 100644 index 515bb60dc2e..00000000000 --- a/changelog/scope-payment-elements-selectors +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Ensure more robust selectors scoping to improve theme compatibility. diff --git a/changelog/test-instructions-item-color b/changelog/test-instructions-item-color deleted file mode 100644 index 4bf5983e8e6..00000000000 --- a/changelog/test-instructions-item-color +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Make test instructions copy icon use the same color as the text next to it diff --git a/changelog/update-1-5316-rename-bank-reference-id b/changelog/update-1-5316-rename-bank-reference-id deleted file mode 100644 index 0a2841c0ad9..00000000000 --- a/changelog/update-1-5316-rename-bank-reference-id +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. diff --git a/changelog/update-7900-payout-notice b/changelog/update-7900-payout-notice deleted file mode 100644 index 4a49df73e41..00000000000 --- a/changelog/update-7900-payout-notice +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Remove payout timing notice and update the help tooltil on Payments Overview page. diff --git a/changelog/update-9910-transaction-id-label b/changelog/update-9910-transaction-id-label deleted file mode 100644 index 0e43652d02b..00000000000 --- a/changelog/update-9910-transaction-id-label +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: Change ID to uppercase in the 'Transaction ID' column label for consistency with similar unique IDs in the UI. - - diff --git a/changelog/update-9916-go-live-modal-and-notice b/changelog/update-9916-go-live-modal-and-notice deleted file mode 100644 index 789b36753a9..00000000000 --- a/changelog/update-9916-go-live-modal-and-notice +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Adjust the go-live modal to match the latest design. diff --git a/changelog/update-confirmation-modal-nox b/changelog/update-confirmation-modal-nox deleted file mode 100644 index 0ffd1af6127..00000000000 --- a/changelog/update-confirmation-modal-nox +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Update confirmation modal after onbarding diff --git a/changelog/update-jetpack-onboarding-flow b/changelog/update-jetpack-onboarding-flow deleted file mode 100644 index a28c6ac383c..00000000000 --- a/changelog/update-jetpack-onboarding-flow +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Update Jetpack onboarding flow diff --git a/changelog/update-pass-capabilities-to-onboarding-as-get-params b/changelog/update-pass-capabilities-to-onboarding-as-get-params deleted file mode 100644 index 9104e7a8f99..00000000000 --- a/changelog/update-pass-capabilities-to-onboarding-as-get-params +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Add support for utilizing NOX capabilities as URL parameters during account creation. diff --git a/changelog/update-server-container-name b/changelog/update-server-container-name deleted file mode 100644 index cb9580f8a22..00000000000 --- a/changelog/update-server-container-name +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Updates server container name used by E2E tests - - diff --git a/changelog/update-to-standalone-jt b/changelog/update-to-standalone-jt deleted file mode 100644 index 4df87f235ec..00000000000 --- a/changelog/update-to-standalone-jt +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Update the tunelling setup. diff --git a/readme.txt b/readme.txt index d67f01c3951..762cea4c9ef 100644 --- a/readme.txt +++ b/readme.txt @@ -94,6 +94,78 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 8.6.1 - 2024-12-17 = +* Add - Add Bank reference key column in Payout reports. This will help reconcile WooPayments Payouts with bank statements. +* Add - Clickwrap terms and conditions support on WooPay +* Add - Display credit card brand icons on order received page. +* Add - Implement gateway method to retrieve recommended payment method. +* Add - Migrate active capabilities from test-drive account when switching to live account. +* Fix - Added conditional use of Jetpack Config callback to avoid i18n notices. +* Fix - Add support to load stripe js asynchronously when it is not immediately available in the global scope. +* Fix - Add the missing "Download" column heading label and toggle menu option to the Payments → Documents list view table. +* Fix - Browser error no longer shows after dispute evidence submission +* Fix - Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. +* Fix - Checkout: Fix error when wc_address_i18n_params does not have data for a given country +* Fix - Consider WooPay eligibility when retrieving WooPay enable state in the settings. +* Fix - Enable ECE for Virtual Variable Subscriptions with Free Trials. +* Fix - Ensure ECE button load events are triggered for multiple buttons on the same page. +* Fix - Ensure ECE is displayed correctly taking into account the tax settings. +* Fix - Ensure ECE login confirmation dialog is shown on Blocks. +* Fix - Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. +* Fix - Errors were incorrectly marked as info in logs. +* Fix - Evidence submission is no longer available for Klarna inquiries as this is not supported by Stripe / Klarna. +* Fix - fix: express checkout to use its own css files. +* Fix - fix: missing ece is_product_page checks +* Fix - fix: undefined $cart_contains_subscription +* Fix - Fix ECE Tracks events not triggering when WooPay is disabled. +* Fix - Fixed Affirm using black logo on dark themes +* Fix - Fixed an issue where order metadata was not updated when capturing an order in the processing state. +* Fix - Fixed UPE country detection in Checkout for non-logged in users +* Fix - Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. +* Fix - Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" +* Fix - Fix styling of transaction details page in mobile view. +* Fix - Fix WooPay component spacing. +* Fix - Fix WooPay trial subscriptions purchases. +* Fix - Make sure the status of manual capture enablement is fetched from the right place. +* Fix - Normalize HK addresses for ECE +* Fix - Order notes for inquiries have clearer content. +* Fix - Prevent express checkout from being used if cart total becomes zero after coupon usage. +* Fix - Remove translations during initialization, preventing unnecessary warnings. +* Fix - Resolved issue with terminal payments in the payment intent failed webhook processing. +* Fix - Restrict Stripe Link to credit card payment method and improve cleanup. +* Fix - Set the support phone field as mandatory in the settings page. +* Fix - Skip mysqlcheck SSL Requirement during E2E environment setup +* Fix - Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. +* Fix - Update Link logo alignment issue when WooPay is enabled and a specific version of Gutenberg is enabled. +* Fix - Use paragraph selector instead of label for pmme appearance +* Fix - Validate required billing fields using data from objects instead of checking the labels. +* Update - Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. +* Update - Adjust the go-live modal to match the latest design. +* Update - Avoid getting the appearance for pay for order page with the wrong appearance key. +* Update - Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. +* Update - chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. +* Update - chore: rename wrapper from payment-request to express-checkout +* Update - Ensure more robust selectors scoping to improve theme compatibility. +* Update - feat: add `wcpay_checkout_use_plain_method_label` filter to allow themes or merchants to force the "plain" WooPayments label on shortcode checkout. +* Update - Make test instructions copy icon use the same color as the text next to it +* Update - refactor: express checkout initialization page location checks +* Update - refactor: express checkout utility for button UI interactions +* Update - Remove payout timing notice and update the help tooltil on Payments Overview page. +* Update - Update confirmation modal after onbarding +* Update - Update Jetpack onboarding flow +* Update - WooPay theming copy in the settings page +* Dev - Add support for utilizing NOX capabilities as URL parameters during account creation. +* Dev - Allow redirect to the settings page from WCPay connect +* Dev - chore: removed old PRB implementation for ApplePay/GooglePay in favor of the ECE implementation; cleaned up ECE feature flag; +* Dev - Disable visual regression testing from Playwright until a more reliable approach is defined. +* Dev - Ensure proper return types in the webhook processing service. +* Dev - fix: E_DEPRECATED on BNPL empty PMME +* Dev - Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. +* Dev - Fix return types +* Dev - Refine verification for disabling ECE on subscriptions that require shipping. +* Dev - Update snapshots for E2E Playwright screenshots +* Dev - Update the tunelling setup. + = 8.6.0 - 2024-12-04 = * Add - Add Bank reference key column in Payout reports. This will help reconcile WooPayments Payouts with bank statements. * Add - Display credit card brand icons on order received page. From d40911012f355a49e59bd43215ac16066b2aa2ee Mon Sep 17 00:00:00 2001 From: Taha Paksu <3295+tpaksu@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:31:20 +0100 Subject: [PATCH 52/83] Revert "Amend changelog entries for release 8.6.1" (#9978) --- changelog.txt | 41 +---------- changelog/8969-fallback-to-card-payment-type | 5 ++ .../add-2253-clickwrap-terms-and-conditions | 4 ++ .../add-6924-migrate-test-drive-capabilities | 4 ++ changelog/add-9690-recommended-pm | 4 ++ changelog/add-jetpack-config-callback | 4 ++ ...imit-woopay-themeing-to-shortcode-checkout | 5 ++ .../add-pass-footer-header-styles-to-woopay | 5 ++ changelog/as-fix-ece-variable-subs | 4 ++ changelog/as-fix-ece-variable-subs-free-trial | 4 ++ changelog/as-hk-address | 4 ++ .../chore-prb-references-in-ece-docs-and-logs | 4 ++ ...e-remove-ece-error-assignment-on-loaderror | 5 ++ ...emove-tokenized-payment-request-references | 5 ++ .../compat-9727-avoid-early-translations | 4 ++ changelog/dev-qit-auth-fix-take-2 | 4 ++ ...nized-ece-product-page-base-implementation | 5 ++ ...ndle-error-on-refund-during-manual-capture | 4 ++ .../fix-7230-payments-details-mobile-view | 4 ++ ...ix-9421-auto-enable-woopay-in-sandbox-mode | 4 ++ changelog/fix-9612-inquiry-order-note | 4 ++ ...ry-payment-activity-transaction-search-css | 4 ++ .../fix-9787-woopay-enable-state-settings | 4 ++ ...-9806-ECE-subscription-checkout-signed-out | 4 ++ ...x-9830-browser-error-on-dispute-submission | 4 ++ changelog/fix-9889-log-level | 4 ++ changelog/fix-add-payment-method-check | 5 ++ ...low-addresses-from-woo-supported-countries | 4 ++ .../fix-change-woopay-theming-settings-copy | 4 ++ .../fix-php-8-compatibility-errors-warnings | 5 ++ .../fix-rounding-error-with-deposit-products | 4 ++ changelog/fix-skip-ssl-requirement-env-setup | 4 ++ changelog/fix-stripe-link-button | 4 ++ .../fix-tokenized-cart-error-notice-json | 5 ++ changelog/fix-upe-country-selection | 4 ++ changelog/fix-upe-theme-block | 4 ++ changelog/fix-use-effect-console-warning | 5 ++ changelog/fix-use-type-is-in-filter | 4 ++ changelog/frosso-patch-1 | 4 ++ changelog/mobile-tpv-tracking-channel | 4 ++ changelog/replace-from-url-query | 4 ++ changelog/scope-payment-elements-selectors | 4 ++ changelog/test-instructions-item-color | 4 ++ .../update-1-5316-rename-bank-reference-id | 4 ++ changelog/update-7900-payout-notice | 4 ++ changelog/update-9910-transaction-id-label | 5 ++ .../update-9916-go-live-modal-and-notice | 4 ++ changelog/update-confirmation-modal-nox | 4 ++ changelog/update-jetpack-onboarding-flow | 4 ++ ...s-capabilities-to-onboarding-as-get-params | 4 ++ changelog/update-server-container-name | 5 ++ changelog/update-to-standalone-jt | 4 ++ readme.txt | 72 ------------------- 53 files changed, 217 insertions(+), 112 deletions(-) create mode 100644 changelog/8969-fallback-to-card-payment-type create mode 100644 changelog/add-2253-clickwrap-terms-and-conditions create mode 100644 changelog/add-6924-migrate-test-drive-capabilities create mode 100644 changelog/add-9690-recommended-pm create mode 100644 changelog/add-jetpack-config-callback create mode 100644 changelog/add-limit-woopay-themeing-to-shortcode-checkout create mode 100644 changelog/add-pass-footer-header-styles-to-woopay create mode 100644 changelog/as-fix-ece-variable-subs create mode 100644 changelog/as-fix-ece-variable-subs-free-trial create mode 100644 changelog/as-hk-address create mode 100644 changelog/chore-prb-references-in-ece-docs-and-logs create mode 100644 changelog/chore-remove-ece-error-assignment-on-loaderror create mode 100644 changelog/chore-remove-tokenized-payment-request-references create mode 100644 changelog/compat-9727-avoid-early-translations create mode 100644 changelog/dev-qit-auth-fix-take-2 create mode 100644 changelog/feat-tokenized-ece-product-page-base-implementation create mode 100644 changelog/fix-5671-handle-error-on-refund-during-manual-capture create mode 100644 changelog/fix-7230-payments-details-mobile-view create mode 100644 changelog/fix-9421-auto-enable-woopay-in-sandbox-mode create mode 100644 changelog/fix-9612-inquiry-order-note create mode 100644 changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css create mode 100644 changelog/fix-9787-woopay-enable-state-settings create mode 100644 changelog/fix-9806-ECE-subscription-checkout-signed-out create mode 100644 changelog/fix-9830-browser-error-on-dispute-submission create mode 100644 changelog/fix-9889-log-level create mode 100644 changelog/fix-add-payment-method-check create mode 100644 changelog/fix-allow-addresses-from-woo-supported-countries create mode 100644 changelog/fix-change-woopay-theming-settings-copy create mode 100644 changelog/fix-php-8-compatibility-errors-warnings create mode 100644 changelog/fix-rounding-error-with-deposit-products create mode 100644 changelog/fix-skip-ssl-requirement-env-setup create mode 100644 changelog/fix-stripe-link-button create mode 100644 changelog/fix-tokenized-cart-error-notice-json create mode 100644 changelog/fix-upe-country-selection create mode 100644 changelog/fix-upe-theme-block create mode 100644 changelog/fix-use-effect-console-warning create mode 100644 changelog/fix-use-type-is-in-filter create mode 100644 changelog/frosso-patch-1 create mode 100644 changelog/mobile-tpv-tracking-channel create mode 100644 changelog/replace-from-url-query create mode 100644 changelog/scope-payment-elements-selectors create mode 100644 changelog/test-instructions-item-color create mode 100644 changelog/update-1-5316-rename-bank-reference-id create mode 100644 changelog/update-7900-payout-notice create mode 100644 changelog/update-9910-transaction-id-label create mode 100644 changelog/update-9916-go-live-modal-and-notice create mode 100644 changelog/update-confirmation-modal-nox create mode 100644 changelog/update-jetpack-onboarding-flow create mode 100644 changelog/update-pass-capabilities-to-onboarding-as-get-params create mode 100644 changelog/update-server-container-name create mode 100644 changelog/update-to-standalone-jt diff --git a/changelog.txt b/changelog.txt index 7661027758a..969e9401b6c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,76 +1,37 @@ *** WooPayments Changelog *** -= 8.6.1 - 2024-12-17 = += 8.6.0 - 2024-12-04 = * Add - Add Bank reference key column in Payout reports. This will help reconcile WooPayments Payouts with bank statements. -* Add - Clickwrap terms and conditions support on WooPay * Add - Display credit card brand icons on order received page. -* Add - Implement gateway method to retrieve recommended payment method. -* Add - Migrate active capabilities from test-drive account when switching to live account. -* Fix - Added conditional use of Jetpack Config callback to avoid i18n notices. * Fix - Add support to load stripe js asynchronously when it is not immediately available in the global scope. * Fix - Add the missing "Download" column heading label and toggle menu option to the Payments → Documents list view table. -* Fix - Browser error no longer shows after dispute evidence submission -* Fix - Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. -* Fix - Checkout: Fix error when wc_address_i18n_params does not have data for a given country -* Fix - Consider WooPay eligibility when retrieving WooPay enable state in the settings. -* Fix - Enable ECE for Virtual Variable Subscriptions with Free Trials. * Fix - Ensure ECE button load events are triggered for multiple buttons on the same page. * Fix - Ensure ECE is displayed correctly taking into account the tax settings. -* Fix - Ensure ECE login confirmation dialog is shown on Blocks. -* Fix - Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. -* Fix - Errors were incorrectly marked as info in logs. * Fix - Evidence submission is no longer available for Klarna inquiries as this is not supported by Stripe / Klarna. * Fix - fix: express checkout to use its own css files. * Fix - fix: missing ece is_product_page checks -* Fix - fix: undefined $cart_contains_subscription * Fix - Fix ECE Tracks events not triggering when WooPay is disabled. -* Fix - Fixed Affirm using black logo on dark themes -* Fix - Fixed an issue where order metadata was not updated when capturing an order in the processing state. -* Fix - Fixed UPE country detection in Checkout for non-logged in users -* Fix - Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. -* Fix - Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" -* Fix - Fix styling of transaction details page in mobile view. * Fix - Fix WooPay component spacing. * Fix - Fix WooPay trial subscriptions purchases. * Fix - Make sure the status of manual capture enablement is fetched from the right place. -* Fix - Normalize HK addresses for ECE -* Fix - Order notes for inquiries have clearer content. * Fix - Prevent express checkout from being used if cart total becomes zero after coupon usage. -* Fix - Remove translations during initialization, preventing unnecessary warnings. * Fix - Resolved issue with terminal payments in the payment intent failed webhook processing. -* Fix - Restrict Stripe Link to credit card payment method and improve cleanup. * Fix - Set the support phone field as mandatory in the settings page. -* Fix - Skip mysqlcheck SSL Requirement during E2E environment setup -* Fix - Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. * Fix - Update Link logo alignment issue when WooPay is enabled and a specific version of Gutenberg is enabled. * Fix - Use paragraph selector instead of label for pmme appearance * Fix - Validate required billing fields using data from objects instead of checking the labels. -* Update - Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. -* Update - Adjust the go-live modal to match the latest design. * Update - Avoid getting the appearance for pay for order page with the wrong appearance key. -* Update - Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. -* Update - chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. * Update - chore: rename wrapper from payment-request to express-checkout -* Update - Ensure more robust selectors scoping to improve theme compatibility. * Update - feat: add `wcpay_checkout_use_plain_method_label` filter to allow themes or merchants to force the "plain" WooPayments label on shortcode checkout. -* Update - Make test instructions copy icon use the same color as the text next to it * Update - refactor: express checkout initialization page location checks * Update - refactor: express checkout utility for button UI interactions -* Update - Remove payout timing notice and update the help tooltil on Payments Overview page. -* Update - Update confirmation modal after onbarding -* Update - Update Jetpack onboarding flow -* Update - WooPay theming copy in the settings page -* Dev - Add support for utilizing NOX capabilities as URL parameters during account creation. * Dev - Allow redirect to the settings page from WCPay connect * Dev - chore: removed old PRB implementation for ApplePay/GooglePay in favor of the ECE implementation; cleaned up ECE feature flag; * Dev - Disable visual regression testing from Playwright until a more reliable approach is defined. * Dev - Ensure proper return types in the webhook processing service. * Dev - fix: E_DEPRECATED on BNPL empty PMME -* Dev - Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. * Dev - Fix return types -* Dev - Refine verification for disabling ECE on subscriptions that require shipping. * Dev - Update snapshots for E2E Playwright screenshots -* Dev - Update the tunelling setup. = 8.5.1 - 2024-11-25 = * Fix - fix: remove "test mode" badge from shortcode checkout. diff --git a/changelog/8969-fallback-to-card-payment-type b/changelog/8969-fallback-to-card-payment-type new file mode 100644 index 00000000000..ee66dbfa7e7 --- /dev/null +++ b/changelog/8969-fallback-to-card-payment-type @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: Small change to payment method types fallback scenario. + + diff --git a/changelog/add-2253-clickwrap-terms-and-conditions b/changelog/add-2253-clickwrap-terms-and-conditions new file mode 100644 index 00000000000..ac0a4ece4b7 --- /dev/null +++ b/changelog/add-2253-clickwrap-terms-and-conditions @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Clickwrap terms and conditions support on WooPay diff --git a/changelog/add-6924-migrate-test-drive-capabilities b/changelog/add-6924-migrate-test-drive-capabilities new file mode 100644 index 00000000000..7b280af4d92 --- /dev/null +++ b/changelog/add-6924-migrate-test-drive-capabilities @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Migrate active capabilities from test-drive account when switching to live account. diff --git a/changelog/add-9690-recommended-pm b/changelog/add-9690-recommended-pm new file mode 100644 index 00000000000..2d615350daa --- /dev/null +++ b/changelog/add-9690-recommended-pm @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Implement gateway method to retrieve recommended payment method. diff --git a/changelog/add-jetpack-config-callback b/changelog/add-jetpack-config-callback new file mode 100644 index 00000000000..64b1a2abb1b --- /dev/null +++ b/changelog/add-jetpack-config-callback @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Added conditional use of Jetpack Config callback to avoid i18n notices. diff --git a/changelog/add-limit-woopay-themeing-to-shortcode-checkout b/changelog/add-limit-woopay-themeing-to-shortcode-checkout new file mode 100644 index 00000000000..4c089593b1f --- /dev/null +++ b/changelog/add-limit-woopay-themeing-to-shortcode-checkout @@ -0,0 +1,5 @@ +Significance: patch +Type: add +Comment: Updates the availability criteria of WooPay Global theme-ing feature. This feature is unreleased and behind a feature flag. + + diff --git a/changelog/add-pass-footer-header-styles-to-woopay b/changelog/add-pass-footer-header-styles-to-woopay new file mode 100644 index 00000000000..ab6375db250 --- /dev/null +++ b/changelog/add-pass-footer-header-styles-to-woopay @@ -0,0 +1,5 @@ +Significance: patch +Type: add +Comment: Impovements to WooPay themeing, which is not yet released to the public. + + diff --git a/changelog/as-fix-ece-variable-subs b/changelog/as-fix-ece-variable-subs new file mode 100644 index 00000000000..236497bcab9 --- /dev/null +++ b/changelog/as-fix-ece-variable-subs @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Refine verification for disabling ECE on subscriptions that require shipping. diff --git a/changelog/as-fix-ece-variable-subs-free-trial b/changelog/as-fix-ece-variable-subs-free-trial new file mode 100644 index 00000000000..64d67393c06 --- /dev/null +++ b/changelog/as-fix-ece-variable-subs-free-trial @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Enable ECE for Virtual Variable Subscriptions with Free Trials. diff --git a/changelog/as-hk-address b/changelog/as-hk-address new file mode 100644 index 00000000000..d58ddb9ffd9 --- /dev/null +++ b/changelog/as-hk-address @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Normalize HK addresses for ECE diff --git a/changelog/chore-prb-references-in-ece-docs-and-logs b/changelog/chore-prb-references-in-ece-docs-and-logs new file mode 100644 index 00000000000..887525ff7bc --- /dev/null +++ b/changelog/chore-prb-references-in-ece-docs-and-logs @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. diff --git a/changelog/chore-remove-ece-error-assignment-on-loaderror b/changelog/chore-remove-ece-error-assignment-on-loaderror new file mode 100644 index 00000000000..cce991d09ba --- /dev/null +++ b/changelog/chore-remove-ece-error-assignment-on-loaderror @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: chore: remove ECE error assignment on loaderror + + diff --git a/changelog/chore-remove-tokenized-payment-request-references b/changelog/chore-remove-tokenized-payment-request-references new file mode 100644 index 00000000000..56dc3b0a0cc --- /dev/null +++ b/changelog/chore-remove-tokenized-payment-request-references @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: chore: remove tokeinzed payment request code + + diff --git a/changelog/compat-9727-avoid-early-translations b/changelog/compat-9727-avoid-early-translations new file mode 100644 index 00000000000..51432b8cd10 --- /dev/null +++ b/changelog/compat-9727-avoid-early-translations @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Remove translations during initialization, preventing unnecessary warnings. diff --git a/changelog/dev-qit-auth-fix-take-2 b/changelog/dev-qit-auth-fix-take-2 new file mode 100644 index 00000000000..67ec99abd79 --- /dev/null +++ b/changelog/dev-qit-auth-fix-take-2 @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. diff --git a/changelog/feat-tokenized-ece-product-page-base-implementation b/changelog/feat-tokenized-ece-product-page-base-implementation new file mode 100644 index 00000000000..e0f342c1623 --- /dev/null +++ b/changelog/feat-tokenized-ece-product-page-base-implementation @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: feat: tokenized ECE product page base implementation + + diff --git a/changelog/fix-5671-handle-error-on-refund-during-manual-capture b/changelog/fix-5671-handle-error-on-refund-during-manual-capture new file mode 100644 index 00000000000..016c68f13aa --- /dev/null +++ b/changelog/fix-5671-handle-error-on-refund-during-manual-capture @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed an issue where order metadata was not updated when capturing an order in the processing state. diff --git a/changelog/fix-7230-payments-details-mobile-view b/changelog/fix-7230-payments-details-mobile-view new file mode 100644 index 00000000000..93e179a44ca --- /dev/null +++ b/changelog/fix-7230-payments-details-mobile-view @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix styling of transaction details page in mobile view. diff --git a/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode b/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode new file mode 100644 index 00000000000..30ec0c7fed5 --- /dev/null +++ b/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. diff --git a/changelog/fix-9612-inquiry-order-note b/changelog/fix-9612-inquiry-order-note new file mode 100644 index 00000000000..3fce0a23430 --- /dev/null +++ b/changelog/fix-9612-inquiry-order-note @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Order notes for inquiries have clearer content. diff --git a/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css b/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css new file mode 100644 index 00000000000..3841ea6164e --- /dev/null +++ b/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. diff --git a/changelog/fix-9787-woopay-enable-state-settings b/changelog/fix-9787-woopay-enable-state-settings new file mode 100644 index 00000000000..cee183680df --- /dev/null +++ b/changelog/fix-9787-woopay-enable-state-settings @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Consider WooPay eligibility when retrieving WooPay enable state in the settings. diff --git a/changelog/fix-9806-ECE-subscription-checkout-signed-out b/changelog/fix-9806-ECE-subscription-checkout-signed-out new file mode 100644 index 00000000000..fa25afd1f10 --- /dev/null +++ b/changelog/fix-9806-ECE-subscription-checkout-signed-out @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ensure ECE login confirmation dialog is shown on Blocks. diff --git a/changelog/fix-9830-browser-error-on-dispute-submission b/changelog/fix-9830-browser-error-on-dispute-submission new file mode 100644 index 00000000000..918ad744351 --- /dev/null +++ b/changelog/fix-9830-browser-error-on-dispute-submission @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Browser error no longer shows after dispute evidence submission diff --git a/changelog/fix-9889-log-level b/changelog/fix-9889-log-level new file mode 100644 index 00000000000..d2f54e24c1a --- /dev/null +++ b/changelog/fix-9889-log-level @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Errors were incorrectly marked as info in logs. diff --git a/changelog/fix-add-payment-method-check b/changelog/fix-add-payment-method-check new file mode 100644 index 00000000000..4ffc9e6342f --- /dev/null +++ b/changelog/fix-add-payment-method-check @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Added a check for the gateway id before comparing it + + diff --git a/changelog/fix-allow-addresses-from-woo-supported-countries b/changelog/fix-allow-addresses-from-woo-supported-countries new file mode 100644 index 00000000000..626fd1ce34f --- /dev/null +++ b/changelog/fix-allow-addresses-from-woo-supported-countries @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Checkout: Fix error when wc_address_i18n_params does not have data for a given country diff --git a/changelog/fix-change-woopay-theming-settings-copy b/changelog/fix-change-woopay-theming-settings-copy new file mode 100644 index 00000000000..fa73b3672f8 --- /dev/null +++ b/changelog/fix-change-woopay-theming-settings-copy @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +WooPay theming copy in the settings page diff --git a/changelog/fix-php-8-compatibility-errors-warnings b/changelog/fix-php-8-compatibility-errors-warnings new file mode 100644 index 00000000000..9c393f71654 --- /dev/null +++ b/changelog/fix-php-8-compatibility-errors-warnings @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: These changes fix some PHP compatibility errors that don't impact WooPayments behaviour. + + diff --git a/changelog/fix-rounding-error-with-deposit-products b/changelog/fix-rounding-error-with-deposit-products new file mode 100644 index 00000000000..d42215e3919 --- /dev/null +++ b/changelog/fix-rounding-error-with-deposit-products @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. diff --git a/changelog/fix-skip-ssl-requirement-env-setup b/changelog/fix-skip-ssl-requirement-env-setup new file mode 100644 index 00000000000..691f98adbfa --- /dev/null +++ b/changelog/fix-skip-ssl-requirement-env-setup @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Skip mysqlcheck SSL Requirement during E2E environment setup diff --git a/changelog/fix-stripe-link-button b/changelog/fix-stripe-link-button new file mode 100644 index 00000000000..d8acf0626f1 --- /dev/null +++ b/changelog/fix-stripe-link-button @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Restrict Stripe Link to credit card payment method and improve cleanup. diff --git a/changelog/fix-tokenized-cart-error-notice-json b/changelog/fix-tokenized-cart-error-notice-json new file mode 100644 index 00000000000..c132e0f7eeb --- /dev/null +++ b/changelog/fix-tokenized-cart-error-notice-json @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: fix: tokenized cart error notice json + + diff --git a/changelog/fix-upe-country-selection b/changelog/fix-upe-country-selection new file mode 100644 index 00000000000..478ffa1cfcd --- /dev/null +++ b/changelog/fix-upe-country-selection @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed UPE country detection in Checkout for non-logged in users diff --git a/changelog/fix-upe-theme-block b/changelog/fix-upe-theme-block new file mode 100644 index 00000000000..6afa59f04d3 --- /dev/null +++ b/changelog/fix-upe-theme-block @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fixed Affirm using black logo on dark themes diff --git a/changelog/fix-use-effect-console-warning b/changelog/fix-use-effect-console-warning new file mode 100644 index 00000000000..45219e7b39a --- /dev/null +++ b/changelog/fix-use-effect-console-warning @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: fix: console warning on plugins page + + diff --git a/changelog/fix-use-type-is-in-filter b/changelog/fix-use-type-is-in-filter new file mode 100644 index 00000000000..3639b203c36 --- /dev/null +++ b/changelog/fix-use-type-is-in-filter @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. diff --git a/changelog/frosso-patch-1 b/changelog/frosso-patch-1 new file mode 100644 index 00000000000..e3812625698 --- /dev/null +++ b/changelog/frosso-patch-1 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +fix: undefined $cart_contains_subscription diff --git a/changelog/mobile-tpv-tracking-channel b/changelog/mobile-tpv-tracking-channel new file mode 100644 index 00000000000..a7b990214df --- /dev/null +++ b/changelog/mobile-tpv-tracking-channel @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. diff --git a/changelog/replace-from-url-query b/changelog/replace-from-url-query new file mode 100644 index 00000000000..58688e1c42f --- /dev/null +++ b/changelog/replace-from-url-query @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" diff --git a/changelog/scope-payment-elements-selectors b/changelog/scope-payment-elements-selectors new file mode 100644 index 00000000000..515bb60dc2e --- /dev/null +++ b/changelog/scope-payment-elements-selectors @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Ensure more robust selectors scoping to improve theme compatibility. diff --git a/changelog/test-instructions-item-color b/changelog/test-instructions-item-color new file mode 100644 index 00000000000..4bf5983e8e6 --- /dev/null +++ b/changelog/test-instructions-item-color @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Make test instructions copy icon use the same color as the text next to it diff --git a/changelog/update-1-5316-rename-bank-reference-id b/changelog/update-1-5316-rename-bank-reference-id new file mode 100644 index 00000000000..0a2841c0ad9 --- /dev/null +++ b/changelog/update-1-5316-rename-bank-reference-id @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. diff --git a/changelog/update-7900-payout-notice b/changelog/update-7900-payout-notice new file mode 100644 index 00000000000..4a49df73e41 --- /dev/null +++ b/changelog/update-7900-payout-notice @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Remove payout timing notice and update the help tooltil on Payments Overview page. diff --git a/changelog/update-9910-transaction-id-label b/changelog/update-9910-transaction-id-label new file mode 100644 index 00000000000..0e43652d02b --- /dev/null +++ b/changelog/update-9910-transaction-id-label @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Change ID to uppercase in the 'Transaction ID' column label for consistency with similar unique IDs in the UI. + + diff --git a/changelog/update-9916-go-live-modal-and-notice b/changelog/update-9916-go-live-modal-and-notice new file mode 100644 index 00000000000..789b36753a9 --- /dev/null +++ b/changelog/update-9916-go-live-modal-and-notice @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Adjust the go-live modal to match the latest design. diff --git a/changelog/update-confirmation-modal-nox b/changelog/update-confirmation-modal-nox new file mode 100644 index 00000000000..0ffd1af6127 --- /dev/null +++ b/changelog/update-confirmation-modal-nox @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update confirmation modal after onbarding diff --git a/changelog/update-jetpack-onboarding-flow b/changelog/update-jetpack-onboarding-flow new file mode 100644 index 00000000000..a28c6ac383c --- /dev/null +++ b/changelog/update-jetpack-onboarding-flow @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Update Jetpack onboarding flow diff --git a/changelog/update-pass-capabilities-to-onboarding-as-get-params b/changelog/update-pass-capabilities-to-onboarding-as-get-params new file mode 100644 index 00000000000..9104e7a8f99 --- /dev/null +++ b/changelog/update-pass-capabilities-to-onboarding-as-get-params @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Add support for utilizing NOX capabilities as URL parameters during account creation. diff --git a/changelog/update-server-container-name b/changelog/update-server-container-name new file mode 100644 index 00000000000..cb9580f8a22 --- /dev/null +++ b/changelog/update-server-container-name @@ -0,0 +1,5 @@ +Significance: patch +Type: dev +Comment: Updates server container name used by E2E tests + + diff --git a/changelog/update-to-standalone-jt b/changelog/update-to-standalone-jt new file mode 100644 index 00000000000..4df87f235ec --- /dev/null +++ b/changelog/update-to-standalone-jt @@ -0,0 +1,4 @@ +Significance: minor +Type: dev + +Update the tunelling setup. diff --git a/readme.txt b/readme.txt index 762cea4c9ef..d67f01c3951 100644 --- a/readme.txt +++ b/readme.txt @@ -94,78 +94,6 @@ Please note that our support for the checkout block is still experimental and th == Changelog == -= 8.6.1 - 2024-12-17 = -* Add - Add Bank reference key column in Payout reports. This will help reconcile WooPayments Payouts with bank statements. -* Add - Clickwrap terms and conditions support on WooPay -* Add - Display credit card brand icons on order received page. -* Add - Implement gateway method to retrieve recommended payment method. -* Add - Migrate active capabilities from test-drive account when switching to live account. -* Fix - Added conditional use of Jetpack Config callback to avoid i18n notices. -* Fix - Add support to load stripe js asynchronously when it is not immediately available in the global scope. -* Fix - Add the missing "Download" column heading label and toggle menu option to the Payments → Documents list view table. -* Fix - Browser error no longer shows after dispute evidence submission -* Fix - Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. -* Fix - Checkout: Fix error when wc_address_i18n_params does not have data for a given country -* Fix - Consider WooPay eligibility when retrieving WooPay enable state in the settings. -* Fix - Enable ECE for Virtual Variable Subscriptions with Free Trials. -* Fix - Ensure ECE button load events are triggered for multiple buttons on the same page. -* Fix - Ensure ECE is displayed correctly taking into account the tax settings. -* Fix - Ensure ECE login confirmation dialog is shown on Blocks. -* Fix - Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. -* Fix - Errors were incorrectly marked as info in logs. -* Fix - Evidence submission is no longer available for Klarna inquiries as this is not supported by Stripe / Klarna. -* Fix - fix: express checkout to use its own css files. -* Fix - fix: missing ece is_product_page checks -* Fix - fix: undefined $cart_contains_subscription -* Fix - Fix ECE Tracks events not triggering when WooPay is disabled. -* Fix - Fixed Affirm using black logo on dark themes -* Fix - Fixed an issue where order metadata was not updated when capturing an order in the processing state. -* Fix - Fixed UPE country detection in Checkout for non-logged in users -* Fix - Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. -* Fix - Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" -* Fix - Fix styling of transaction details page in mobile view. -* Fix - Fix WooPay component spacing. -* Fix - Fix WooPay trial subscriptions purchases. -* Fix - Make sure the status of manual capture enablement is fetched from the right place. -* Fix - Normalize HK addresses for ECE -* Fix - Order notes for inquiries have clearer content. -* Fix - Prevent express checkout from being used if cart total becomes zero after coupon usage. -* Fix - Remove translations during initialization, preventing unnecessary warnings. -* Fix - Resolved issue with terminal payments in the payment intent failed webhook processing. -* Fix - Restrict Stripe Link to credit card payment method and improve cleanup. -* Fix - Set the support phone field as mandatory in the settings page. -* Fix - Skip mysqlcheck SSL Requirement during E2E environment setup -* Fix - Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. -* Fix - Update Link logo alignment issue when WooPay is enabled and a specific version of Gutenberg is enabled. -* Fix - Use paragraph selector instead of label for pmme appearance -* Fix - Validate required billing fields using data from objects instead of checking the labels. -* Update - Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. -* Update - Adjust the go-live modal to match the latest design. -* Update - Avoid getting the appearance for pay for order page with the wrong appearance key. -* Update - Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. -* Update - chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. -* Update - chore: rename wrapper from payment-request to express-checkout -* Update - Ensure more robust selectors scoping to improve theme compatibility. -* Update - feat: add `wcpay_checkout_use_plain_method_label` filter to allow themes or merchants to force the "plain" WooPayments label on shortcode checkout. -* Update - Make test instructions copy icon use the same color as the text next to it -* Update - refactor: express checkout initialization page location checks -* Update - refactor: express checkout utility for button UI interactions -* Update - Remove payout timing notice and update the help tooltil on Payments Overview page. -* Update - Update confirmation modal after onbarding -* Update - Update Jetpack onboarding flow -* Update - WooPay theming copy in the settings page -* Dev - Add support for utilizing NOX capabilities as URL parameters during account creation. -* Dev - Allow redirect to the settings page from WCPay connect -* Dev - chore: removed old PRB implementation for ApplePay/GooglePay in favor of the ECE implementation; cleaned up ECE feature flag; -* Dev - Disable visual regression testing from Playwright until a more reliable approach is defined. -* Dev - Ensure proper return types in the webhook processing service. -* Dev - fix: E_DEPRECATED on BNPL empty PMME -* Dev - Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. -* Dev - Fix return types -* Dev - Refine verification for disabling ECE on subscriptions that require shipping. -* Dev - Update snapshots for E2E Playwright screenshots -* Dev - Update the tunelling setup. - = 8.6.0 - 2024-12-04 = * Add - Add Bank reference key column in Payout reports. This will help reconcile WooPayments Payouts with bank statements. * Add - Display credit card brand icons on order received page. From 0c7422e78b84f23247d56db031aa0fde5b49a0e9 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Tue, 17 Dec 2024 17:19:13 +0100 Subject: [PATCH 53/83] Move Promise instantiation into memoized function to ensure caching consistency (#9927) Co-authored-by: Timur Karimov --- changelog/fix-unhandled-promises | 4 + client/express-checkout/blocks/index.js | 8 +- .../utils/checkPaymentMethodIsAvailable.js | 122 +++++++++--------- 3 files changed, 69 insertions(+), 65 deletions(-) create mode 100644 changelog/fix-unhandled-promises diff --git a/changelog/fix-unhandled-promises b/changelog/fix-unhandled-promises new file mode 100644 index 00000000000..a4d1a679405 --- /dev/null +++ b/changelog/fix-unhandled-promises @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Fix payment method filtering when billing country changes in Blocks checkout. diff --git a/client/express-checkout/blocks/index.js b/client/express-checkout/blocks/index.js index 764dc2292cd..a46ef99e82b 100644 --- a/client/express-checkout/blocks/index.js +++ b/client/express-checkout/blocks/index.js @@ -39,9 +39,7 @@ const expressCheckoutElementApplePay = ( api ) => ( { return false; } - return new Promise( ( resolve ) => { - checkPaymentMethodIsAvailable( 'applePay', cart, resolve ); - } ); + return checkPaymentMethodIsAvailable( 'applePay', cart ); }, } ); @@ -77,9 +75,7 @@ const expressCheckoutElementGooglePay = ( api ) => { return false; } - return new Promise( ( resolve ) => { - checkPaymentMethodIsAvailable( 'googlePay', cart, resolve ); - } ); + return checkPaymentMethodIsAvailable( 'googlePay', cart ); }, }; }; diff --git a/client/express-checkout/utils/checkPaymentMethodIsAvailable.js b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js index b592169da22..5beb7e32942 100644 --- a/client/express-checkout/utils/checkPaymentMethodIsAvailable.js +++ b/client/express-checkout/utils/checkPaymentMethodIsAvailable.js @@ -14,71 +14,75 @@ import WCPayAPI from 'wcpay/checkout/api'; import { getUPEConfig } from 'wcpay/utils/checkout'; export const checkPaymentMethodIsAvailable = memoize( - ( paymentMethod, cart, resolve ) => { - // Create the DIV container on the fly - const containerEl = document.createElement( 'div' ); + ( paymentMethod, cart ) => { + return new Promise( ( resolve ) => { + // Create the DIV container on the fly + const containerEl = document.createElement( 'div' ); - // Ensure the element is hidden and doesn’t interfere with the page layout. - containerEl.style.display = 'none'; + // Ensure the element is hidden and doesn’t interfere with the page layout. + containerEl.style.display = 'none'; - document.querySelector( 'body' ).appendChild( containerEl ); + document.querySelector( 'body' ).appendChild( containerEl ); - const root = ReactDOM.createRoot( containerEl ); + const root = ReactDOM.createRoot( containerEl ); - const api = new WCPayAPI( - { - publishableKey: getUPEConfig( 'publishableKey' ), - accountId: getUPEConfig( 'accountId' ), - forceNetworkSavedCards: getUPEConfig( - 'forceNetworkSavedCards' - ), - locale: getUPEConfig( 'locale' ), - isStripeLinkEnabled: isLinkEnabled( - getUPEConfig( 'paymentMethodsConfig' ) - ), - }, - request - ); + const api = new WCPayAPI( + { + publishableKey: getUPEConfig( 'publishableKey' ), + accountId: getUPEConfig( 'accountId' ), + forceNetworkSavedCards: getUPEConfig( + 'forceNetworkSavedCards' + ), + locale: getUPEConfig( 'locale' ), + isStripeLinkEnabled: isLinkEnabled( + getUPEConfig( 'paymentMethodsConfig' ) + ), + }, + request + ); - root.render( - - resolve( false ) } + root.render( + { - let canMakePayment = false; - if ( event.availablePaymentMethods ) { - canMakePayment = - event.availablePaymentMethods[ paymentMethod ]; - } - resolve( canMakePayment ); - root.unmount(); - containerEl.remove(); - } } - /> - - ); + > + resolve( false ) } + options={ { + paymentMethods: { + amazonPay: 'never', + applePay: + paymentMethod === 'applePay' + ? 'always' + : 'never', + googlePay: + paymentMethod === 'googlePay' + ? 'always' + : 'never', + link: 'never', + paypal: 'never', + }, + } } + onReady={ ( event ) => { + let canMakePayment = false; + if ( event.availablePaymentMethods ) { + canMakePayment = + event.availablePaymentMethods[ + paymentMethod + ]; + } + resolve( canMakePayment ); + root.unmount(); + containerEl.remove(); + } } + /> + + ); + } ); } ); From 129fe05703a44bb4085b746e540865e1e6a18995 Mon Sep 17 00:00:00 2001 From: Rafael Zaleski Date: Tue, 17 Dec 2024 15:45:40 -0300 Subject: [PATCH 54/83] Refresh page when ECE is dismissed (#9888) --- .../fix-9794-refresh-page-when-ece-dismissed | 4 + client/express-checkout/event-handlers.js | 10 ++ client/express-checkout/utils/index.ts | 1 + .../express-checkout/utils/shipping-fields.js | 131 ++++++++++++++++++ .../event-handlers.js | 14 +- .../tokenized-express-checkout/utils/index.ts | 1 + .../utils/shipping-fields.js | 131 ++++++++++++++++++ 7 files changed, 291 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-9794-refresh-page-when-ece-dismissed create mode 100644 client/express-checkout/utils/shipping-fields.js create mode 100644 client/tokenized-express-checkout/utils/shipping-fields.js diff --git a/changelog/fix-9794-refresh-page-when-ece-dismissed b/changelog/fix-9794-refresh-page-when-ece-dismissed new file mode 100644 index 00000000000..7ec81b4760e --- /dev/null +++ b/changelog/fix-9794-refresh-page-when-ece-dismissed @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Refresh the cart and checkout pages when ECE is dismissed and the shipping options were modified in the payment sheet. diff --git a/client/express-checkout/event-handlers.js b/client/express-checkout/event-handlers.js index 2d1345ff752..3c59d456251 100644 --- a/client/express-checkout/event-handlers.js +++ b/client/express-checkout/event-handlers.js @@ -13,12 +13,15 @@ import { normalizeShippingAddress, normalizeLineItems, getExpressCheckoutData, + updateShippingAddressUI, } from './utils'; import { trackExpressCheckoutButtonClick, trackExpressCheckoutButtonLoad, } from './tracking'; +let lastSelectedAddress = null; + export const shippingAddressChangeHandler = async ( api, event, elements ) => { try { const response = await api.expressCheckoutECECalculateShippingOptions( @@ -29,6 +32,9 @@ export const shippingAddressChangeHandler = async ( api, event, elements ) => { elements.update( { amount: response.total.amount, } ); + + lastSelectedAddress = event.address; + event.resolve( { shippingRates: response.shipping_options, lineItems: normalizeLineItems( response.displayItems ), @@ -171,5 +177,9 @@ export const onCompletePaymentHandler = () => { }; export const onCancelHandler = () => { + if ( lastSelectedAddress ) { + updateShippingAddressUI( lastSelectedAddress ); + } + lastSelectedAddress = null; unblockUI(); }; diff --git a/client/express-checkout/utils/index.ts b/client/express-checkout/utils/index.ts index 7e6c4bf2d09..3fcb6286071 100644 --- a/client/express-checkout/utils/index.ts +++ b/client/express-checkout/utils/index.ts @@ -2,6 +2,7 @@ * Internal dependencies */ export * from './normalize'; +export * from './shipping-fields'; import { getDefaultBorderRadius } from 'wcpay/utils/express-checkout'; interface MyWindow extends Window { diff --git a/client/express-checkout/utils/shipping-fields.js b/client/express-checkout/utils/shipping-fields.js new file mode 100644 index 00000000000..f097b1eca59 --- /dev/null +++ b/client/express-checkout/utils/shipping-fields.js @@ -0,0 +1,131 @@ +/* global jQuery */ +/** + * Internal dependencies + */ +import { normalizeShippingAddress, getExpressCheckoutData } from '.'; + +/** + * Checks if the intermediate address is redacted for the given country. + * CA and GB addresses are redacted and are causing errors until WooCommerce is able to + * handle redacted addresses. + * https://developers.google.com/pay/api/web/reference/response-objects#IntermediateAddress + * + * @param {string} country - The country code. + * + * @return {boolean} True if the postcode is redacted for the country, false otherwise. + */ +const isPostcodeRedactedForCountry = ( country ) => { + return [ 'CA', 'GB' ].includes( country ); +}; + +/* + * Updates a field in a form with a new value. + * + * @param {String} formSelector - The selector for the form containing the field. + * @param {Object} fieldName - The name of the field to update. + * @param {Object} value - The new value for the field. + */ +const updateShortcodeField = ( formSelector, fieldName, value ) => { + const field = document.querySelector( + `${ formSelector } [name="${ fieldName }"]` + ); + + if ( ! field ) return; + + // Check if the field is a dropdown (country/state). + if ( field.tagName === 'SELECT' && /country|state/.test( fieldName ) ) { + const options = Array.from( field.options ); + const match = options.find( + ( opt ) => + opt.value === value || + opt.textContent.trim().toLowerCase() === value.toLowerCase() + ); + + if ( match ) { + field.value = match.value; + jQuery( field ).trigger( 'change' ).trigger( 'close' ); + } + } else { + // Default behavior for text inputs. + field.value = value; + jQuery( field ).trigger( 'change' ); + } +}; + +/** + * Updates the WooCommerce Blocks shipping UI to reflect a new shipping address. + * + * @param {Object} eventAddress - The shipping address returned by the payment event. + */ +const updateBlocksShippingUI = ( eventAddress ) => { + wp?.data + ?.dispatch( 'wc/store/cart' ) + ?.setShippingAddress( normalizeShippingAddress( eventAddress ) ); +}; + +/** + * Updates the WooCommerce shortcode cart/checkout shipping UI to reflect a new shipping address. + * + * @param {Object} eventAddress - The shipping address returned by the payment event. + */ +const updateShortcodeShippingUI = ( eventAddress ) => { + const context = getExpressCheckoutData( 'button_context' ); + const address = normalizeShippingAddress( eventAddress ); + + const keys = [ 'country', 'state', 'city', 'postcode' ]; + + if ( context === 'cart' ) { + keys.forEach( ( key ) => { + if ( address[ key ] ) { + updateShortcodeField( + 'form.woocommerce-shipping-calculator', + `calc_shipping_${ key }`, + address[ key ] + ); + } + } ); + document + .querySelector( + 'form.woocommerce-shipping-calculator [name="calc_shipping"]' + ) + ?.click(); + } else if ( context === 'checkout' ) { + keys.forEach( ( key ) => { + if ( address[ key ] ) { + updateShortcodeField( + 'form.woocommerce-checkout', + `billing_${ key }`, + address[ key ] + ); + } + } ); + } +}; + +/** + * Updates the WooCommerce shipping UI to reflect a new shipping address. + * + * Determines the current context (cart or checkout) and updates either + * WooCommerce Blocks or shortcode-based shipping forms, if applicable. + * + * @param {Object} newAddress - The new shipping address object returned by the payment event. + * @param {string} newAddress.country - The country code of the shipping address. + * @param {string} [newAddress.state] - The state/province of the shipping address. + * @param {string} [newAddress.city] - The city of the shipping address. + * @param {string} [newAddress.postcode] - The postal/ZIP code of the shipping address. + */ +export const updateShippingAddressUI = ( newAddress ) => { + const context = getExpressCheckoutData( 'button_context' ); + const isBlocks = getExpressCheckoutData( 'has_block' ); + + if ( + [ 'cart', 'checkout' ].includes( context ) && + ! isPostcodeRedactedForCountry( newAddress.country ) + ) { + if ( isBlocks ) { + updateBlocksShippingUI( newAddress ); + } else { + updateShortcodeShippingUI( newAddress ); + } + } +}; diff --git a/client/tokenized-express-checkout/event-handlers.js b/client/tokenized-express-checkout/event-handlers.js index 12ec2513cc7..db2bc4c2c3e 100644 --- a/client/tokenized-express-checkout/event-handlers.js +++ b/client/tokenized-express-checkout/event-handlers.js @@ -8,7 +8,11 @@ import { applyFilters } from '@wordpress/hooks'; /** * Internal dependencies */ -import { getErrorMessageFromNotice, getExpressCheckoutData } from './utils'; +import { + getErrorMessageFromNotice, + getExpressCheckoutData, + updateShippingAddressUI, +} from './utils'; import { trackExpressCheckoutButtonClick, trackExpressCheckoutButtonLoad, @@ -24,6 +28,7 @@ import { transformPrice, } from './transformers/wc-to-stripe'; +let lastSelectedAddress = null; let cartApi = new ExpressCheckoutCartApi(); export const setCartApiHandler = ( handler ) => ( cartApi = handler ); export const getCartApiHandler = () => cartApi; @@ -56,6 +61,9 @@ export const shippingAddressChangeHandler = async ( event, elements ) => { cartData.totals ), } ); + + lastSelectedAddress = event.address; + event.resolve( { shippingRates: transformCartDataForShippingRates( cartData ), lineItems: transformCartDataForDisplayItems( cartData ), @@ -216,5 +224,9 @@ export const onCompletePaymentHandler = () => { }; export const onCancelHandler = () => { + if ( lastSelectedAddress ) { + updateShippingAddressUI( lastSelectedAddress ); + } + lastSelectedAddress = null; unblockUI(); }; diff --git a/client/tokenized-express-checkout/utils/index.ts b/client/tokenized-express-checkout/utils/index.ts index 98ee8b90091..bae962363b2 100644 --- a/client/tokenized-express-checkout/utils/index.ts +++ b/client/tokenized-express-checkout/utils/index.ts @@ -3,6 +3,7 @@ */ import { WCPayExpressCheckoutParams } from 'wcpay/express-checkout/utils'; export * from './normalize'; +export * from './shipping-fields'; import { getDefaultBorderRadius } from 'wcpay/utils/express-checkout'; export const getExpressCheckoutData = < diff --git a/client/tokenized-express-checkout/utils/shipping-fields.js b/client/tokenized-express-checkout/utils/shipping-fields.js new file mode 100644 index 00000000000..f097b1eca59 --- /dev/null +++ b/client/tokenized-express-checkout/utils/shipping-fields.js @@ -0,0 +1,131 @@ +/* global jQuery */ +/** + * Internal dependencies + */ +import { normalizeShippingAddress, getExpressCheckoutData } from '.'; + +/** + * Checks if the intermediate address is redacted for the given country. + * CA and GB addresses are redacted and are causing errors until WooCommerce is able to + * handle redacted addresses. + * https://developers.google.com/pay/api/web/reference/response-objects#IntermediateAddress + * + * @param {string} country - The country code. + * + * @return {boolean} True if the postcode is redacted for the country, false otherwise. + */ +const isPostcodeRedactedForCountry = ( country ) => { + return [ 'CA', 'GB' ].includes( country ); +}; + +/* + * Updates a field in a form with a new value. + * + * @param {String} formSelector - The selector for the form containing the field. + * @param {Object} fieldName - The name of the field to update. + * @param {Object} value - The new value for the field. + */ +const updateShortcodeField = ( formSelector, fieldName, value ) => { + const field = document.querySelector( + `${ formSelector } [name="${ fieldName }"]` + ); + + if ( ! field ) return; + + // Check if the field is a dropdown (country/state). + if ( field.tagName === 'SELECT' && /country|state/.test( fieldName ) ) { + const options = Array.from( field.options ); + const match = options.find( + ( opt ) => + opt.value === value || + opt.textContent.trim().toLowerCase() === value.toLowerCase() + ); + + if ( match ) { + field.value = match.value; + jQuery( field ).trigger( 'change' ).trigger( 'close' ); + } + } else { + // Default behavior for text inputs. + field.value = value; + jQuery( field ).trigger( 'change' ); + } +}; + +/** + * Updates the WooCommerce Blocks shipping UI to reflect a new shipping address. + * + * @param {Object} eventAddress - The shipping address returned by the payment event. + */ +const updateBlocksShippingUI = ( eventAddress ) => { + wp?.data + ?.dispatch( 'wc/store/cart' ) + ?.setShippingAddress( normalizeShippingAddress( eventAddress ) ); +}; + +/** + * Updates the WooCommerce shortcode cart/checkout shipping UI to reflect a new shipping address. + * + * @param {Object} eventAddress - The shipping address returned by the payment event. + */ +const updateShortcodeShippingUI = ( eventAddress ) => { + const context = getExpressCheckoutData( 'button_context' ); + const address = normalizeShippingAddress( eventAddress ); + + const keys = [ 'country', 'state', 'city', 'postcode' ]; + + if ( context === 'cart' ) { + keys.forEach( ( key ) => { + if ( address[ key ] ) { + updateShortcodeField( + 'form.woocommerce-shipping-calculator', + `calc_shipping_${ key }`, + address[ key ] + ); + } + } ); + document + .querySelector( + 'form.woocommerce-shipping-calculator [name="calc_shipping"]' + ) + ?.click(); + } else if ( context === 'checkout' ) { + keys.forEach( ( key ) => { + if ( address[ key ] ) { + updateShortcodeField( + 'form.woocommerce-checkout', + `billing_${ key }`, + address[ key ] + ); + } + } ); + } +}; + +/** + * Updates the WooCommerce shipping UI to reflect a new shipping address. + * + * Determines the current context (cart or checkout) and updates either + * WooCommerce Blocks or shortcode-based shipping forms, if applicable. + * + * @param {Object} newAddress - The new shipping address object returned by the payment event. + * @param {string} newAddress.country - The country code of the shipping address. + * @param {string} [newAddress.state] - The state/province of the shipping address. + * @param {string} [newAddress.city] - The city of the shipping address. + * @param {string} [newAddress.postcode] - The postal/ZIP code of the shipping address. + */ +export const updateShippingAddressUI = ( newAddress ) => { + const context = getExpressCheckoutData( 'button_context' ); + const isBlocks = getExpressCheckoutData( 'has_block' ); + + if ( + [ 'cart', 'checkout' ].includes( context ) && + ! isPostcodeRedactedForCountry( newAddress.country ) + ) { + if ( isBlocks ) { + updateBlocksShippingUI( newAddress ); + } else { + updateShortcodeShippingUI( newAddress ); + } + } +}; From cf6d2c1f5de1df9d60c9f040757fd35a79525b35 Mon Sep 17 00:00:00 2001 From: bruce aldridge Date: Wed, 18 Dec 2024 11:09:21 +1300 Subject: [PATCH 55/83] Performance improvements for disputes reminder task list (#9906) --- changelog/fix-9716-disputes-api-requests | 4 ++ includes/admin/class-wc-payments-admin.php | 1 + .../tasks/class-wc-payments-task-disputes.php | 42 ++++++++++++++----- includes/class-wc-payments-tasks.php | 10 ++++- .../test-class-wc-payments-task-disputes.php | 1 + 5 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 changelog/fix-9716-disputes-api-requests diff --git a/changelog/fix-9716-disputes-api-requests b/changelog/fix-9716-disputes-api-requests new file mode 100644 index 00000000000..10f5387c9b4 --- /dev/null +++ b/changelog/fix-9716-disputes-api-requests @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Performance improvements for Disputes Needing Response task shown in WooCommerce admin. diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index 6509cc90ecb..e7ad01fe210 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -1336,6 +1336,7 @@ public function add_transactions_notification_badge() { /** * Gets the number of disputes which need a response. ie have a 'needs_response' or 'warning_needs_response' status. + * Used to display a notification badge on the Payments > Disputes menu item. * * @return int The number of disputes which need a response. */ diff --git a/includes/admin/tasks/class-wc-payments-task-disputes.php b/includes/admin/tasks/class-wc-payments-task-disputes.php index b7212ec7623..7d5ac82faf4 100644 --- a/includes/admin/tasks/class-wc-payments-task-disputes.php +++ b/includes/admin/tasks/class-wc-payments-task-disputes.php @@ -49,6 +49,13 @@ class WC_Payments_Task_Disputes extends Task { */ private $disputes_due_within_1d; + /** + * A memory cache of all disputes needing response. + * + * @var array|null + */ + private $disputes_needing_response = null; + /** * WC_Payments_Task_Disputes constructor. */ @@ -57,13 +64,12 @@ public function __construct() { $this->api_client = \WC_Payments::get_payments_api_client(); $this->database_cache = \WC_Payments::get_database_cache(); parent::__construct(); - $this->init(); } /** * Initialize the task. */ - private function init() { + private function fetch_relevant_disputes() { $this->disputes_due_within_7d = $this->get_disputes_needing_response_within_days( 7 ); $this->disputes_due_within_1d = $this->get_disputes_needing_response_within_days( 1 ); } @@ -83,6 +89,9 @@ public function get_id() { * @return string */ public function get_title() { + if ( null === $this->disputes_needing_response ) { + $this->fetch_relevant_disputes(); + } if ( count( (array) $this->disputes_due_within_7d ) === 1 ) { $dispute = $this->disputes_due_within_7d[0]; $amount = WC_Payments_Utils::interpret_stripe_amount( $dispute['amount'], $dispute['currency'] ); @@ -275,6 +284,9 @@ public function is_complete() { * @return bool */ public function can_view() { + if ( null === $this->disputes_needing_response ) { + $this->fetch_relevant_disputes(); + } return count( (array) $this->disputes_due_within_7d ) > 0; } @@ -322,15 +334,24 @@ private function get_disputes_needing_response_within_days( $num_days ) { * @return array|null Array of disputes awaiting a response. Null on failure. */ private function get_disputes_needing_response() { - return $this->database_cache->get_or_add( + if ( null !== $this->disputes_needing_response ) { + return $this->disputes_needing_response; + } + + $this->disputes_needing_response = $this->database_cache->get_or_add( Database_Cache::ACTIVE_DISPUTES_KEY, function () { - $response = $this->api_client->get_disputes( - [ - 'pagesize' => 50, - 'search' => [ 'warning_needs_response', 'needs_response' ], - ] - ); + try { + $response = $this->api_client->get_disputes( + [ + 'pagesize' => 50, + 'search' => [ 'warning_needs_response', 'needs_response' ], + ] + ); + } catch ( \Exception $e ) { + // Ensure an array is always returned, even if the API call fails. + return []; + } $active_disputes = $response['data'] ?? []; @@ -347,8 +368,9 @@ function ( $a, $b ) { return $active_disputes; }, - // We'll consider all array values to be valid as the cache is only invalidated when it is deleted or it expires. 'is_array' ); + + return $this->disputes_needing_response; } } diff --git a/includes/class-wc-payments-tasks.php b/includes/class-wc-payments-tasks.php index ee3feacff48..b0b01e22896 100644 --- a/includes/class-wc-payments-tasks.php +++ b/includes/class-wc-payments-tasks.php @@ -21,7 +21,11 @@ class WC_Payments_Tasks { * WC_Payments_Admin_Tasks constructor. */ public static function init() { - include_once WCPAY_ABSPATH . 'includes/admin/tasks/class-wc-payments-task-disputes.php'; + // As WooCommerce Onboarding tasks need to hook into 'init' and requires an API call. + // We only add this task for users who can manage_woocommerce / view the task. + if ( ! current_user_can( 'manage_woocommerce' ) ) { + return; + } add_action( 'init', [ __CLASS__, 'add_task_disputes_need_response' ] ); } @@ -31,9 +35,11 @@ public static function init() { */ public static function add_task_disputes_need_response() { $account_service = WC_Payments::get_account_service(); - if ( ! $account_service || ! $account_service->is_stripe_account_valid() ) { + // The task is not required if the account is not connected, under review, or rejected. + if ( ! $account_service || ! $account_service->is_stripe_account_valid() || $account_service->is_account_under_review() || $account_service->is_account_rejected() ) { return; } + include_once WCPAY_ABSPATH . 'includes/admin/tasks/class-wc-payments-task-disputes.php'; // 'extended' = 'Things to do next' task list on WooCommerce > Home. TaskLists::add_task( 'extended', new WC_Payments_Task_Disputes() ); diff --git a/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php b/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php index 43771de278d..729be9743f0 100644 --- a/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php +++ b/tests/unit/admin/tasks/test-class-wc-payments-task-disputes.php @@ -8,6 +8,7 @@ use WCPay\Constants\Country_Code; use WooCommerce\Payments\Tasks\WC_Payments_Task_Disputes; +require_once WCPAY_ABSPATH . 'includes/admin/tasks/class-wc-payments-task-disputes.php'; /** * WC_Payments_Task_Disputes unit tests. */ From 99db1fb899286f547481e0ef9969827b896eb606 Mon Sep 17 00:00:00 2001 From: Francesco Date: Wed, 18 Dec 2024 10:05:16 +0100 Subject: [PATCH 56/83] fix: tokenized cart w/ multiple variations (#9979) --- .../fix-tokenized-cart-multiple-variations | 5 +++++ client/tokenized-express-checkout/index.js | 17 ++++++++++++++++- .../transformers/wc-to-stripe.js | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-tokenized-cart-multiple-variations diff --git a/changelog/fix-tokenized-cart-multiple-variations b/changelog/fix-tokenized-cart-multiple-variations new file mode 100644 index 00000000000..5d155cd5513 --- /dev/null +++ b/changelog/fix-tokenized-cart-multiple-variations @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: fix: tokenized cart & multiple variations. + + diff --git a/client/tokenized-express-checkout/index.js b/client/tokenized-express-checkout/index.js index eca8a2f8a36..3b2eb1a8b46 100644 --- a/client/tokenized-express-checkout/index.js +++ b/client/tokenized-express-checkout/index.js @@ -326,6 +326,21 @@ jQuery( ( $ ) => { 'wcpay.express-checkout.update-button-data', 'automattic/wcpay/express-checkout', async () => { + // if the product cannot be added to cart (because of missing variation selection, etc), + // don't try to add it to the cart to get new data - the call will likely fail. + if ( + getExpressCheckoutData( 'button_context' ) === 'product' + ) { + const addToCartButton = $( + '.single_add_to_cart_button' + ); + + // First check if product can be added to cart. + if ( addToCartButton.is( '.disabled' ) ) { + return; + } + } + try { expressCheckoutButtonUi.blockButton(); @@ -375,7 +390,7 @@ jQuery( ( $ ) => { expressCheckoutButtonUi.unblockButton(); } catch ( e ) { - expressCheckoutButtonUi.hide(); + expressCheckoutButtonUi.hideContainer(); } } ); diff --git a/client/tokenized-express-checkout/transformers/wc-to-stripe.js b/client/tokenized-express-checkout/transformers/wc-to-stripe.js index 867a389006b..7c4c9c14822 100644 --- a/client/tokenized-express-checkout/transformers/wc-to-stripe.js +++ b/client/tokenized-express-checkout/transformers/wc-to-stripe.js @@ -96,7 +96,7 @@ export const transformCartDataForDisplayItems = ( cartData ) => { * @return {{id: string, label: string, amount: integer, deliveryEstimate: string}} `shippingRates` for Stripe. */ export const transformCartDataForShippingRates = ( cartData ) => - cartData.shipping_rates?.[ 0 ].shipping_rates + cartData.shipping_rates?.[ 0 ]?.shipping_rates .sort( ( rateA, rateB ) => { if ( rateA.selected === rateB.selected ) { return 0; // Keep relative order if both have the same value for 'selected' From bf363d636c639525a0fe5a1039e5bc93e4a6c08e Mon Sep 17 00:00:00 2001 From: Miguel Gasca Date: Wed, 18 Dec 2024 13:29:53 +0100 Subject: [PATCH 57/83] Improve message shown to merchants when there is an error capturing/cancelling pending authorizations (#9937) --- ...there-is-an-error-capturing-authorizations | 4 + client/data/authorizations/actions.ts | 102 +++++++++-- .../data/authorizations/test/actions.test.ts | 159 +++++++++++++++++- 3 files changed, 247 insertions(+), 18 deletions(-) create mode 100644 changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations diff --git a/changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations b/changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations new file mode 100644 index 00000000000..b76d70e1cf9 --- /dev/null +++ b/changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update error messages for payment authorization actions to provide more specific and user-friendly feedback. diff --git a/client/data/authorizations/actions.ts b/client/data/authorizations/actions.ts index 0eaf50f105d..0885da1cbfe 100644 --- a/client/data/authorizations/actions.ts +++ b/client/data/authorizations/actions.ts @@ -19,6 +19,51 @@ import { import { STORE_NAME } from '../constants'; import { ApiError } from 'wcpay/types/errors'; +const getErrorMessage = ( apiError: { + code?: string; + message?: string; +} ): string => { + // Map specific error codes to user-friendly messages + const errorMessages: Record< string, string > = { + wcpay_missing_order: __( + 'The order could not be found.', + 'woocommerce-payments' + ), + wcpay_refunded_order_uncapturable: __( + 'Payment cannot be processed for partially or fully refunded orders.', + 'woocommerce-payments' + ), + wcpay_intent_order_mismatch: __( + 'The payment cannot be processed due to a mismatch with order details.', + 'woocommerce-payments' + ), + wcpay_payment_uncapturable: __( + 'This payment cannot be processed in its current state.', + 'woocommerce-payments' + ), + wcpay_capture_error: __( + 'The payment capture failed to complete.', + 'woocommerce-payments' + ), + wcpay_cancel_error: __( + 'The payment cancellation failed to complete.', + 'woocommerce-payments' + ), + wcpay_server_error: __( + 'An unexpected error occurred. Please try again later.', + 'woocommerce-payments' + ), + }; + + return ( + errorMessages[ apiError.code ?? '' ] ?? + __( + 'Unable to process the payment. Please try again later.', + 'woocommerce-payments' + ) + ); +}; + export function updateAuthorizations( query: Query, data: Authorization[] @@ -165,17 +210,29 @@ export function* submitCaptureAuthorization( ) ); } catch ( error ) { + const baseErrorMessage = sprintf( + // translators: %s Order id + __( + 'There has been an error capturing the payment for order #%s.', + 'woocommerce-payments' + ), + orderId + ); + + const apiError = error as { + code?: string; + message?: string; + data?: { + status?: number; + }; + }; + + const errorDetails = getErrorMessage( apiError ); + yield controls.dispatch( 'core/notices', 'createErrorNotice', - sprintf( - // translators: %s Order id - __( - 'There has been an error capturing the payment for order #%s. Please try again later.', - 'woocommerce-payments' - ), - orderId - ) + `${ baseErrorMessage } ${ errorDetails }` ); } finally { yield controls.dispatch( @@ -184,6 +241,7 @@ export function* submitCaptureAuthorization( 'getAuthorization', [ paymentIntentId ] ); + yield controls.dispatch( STORE_NAME, 'setIsRequestingAuthorization', @@ -278,17 +336,29 @@ export function* submitCancelAuthorization( ) ); } catch ( error ) { + const baseErrorMessage = sprintf( + // translators: %s Order id + __( + 'There has been an error canceling the payment for order #%s.', + 'woocommerce-payments' + ), + orderId + ); + + const apiError = error as { + code?: string; + message?: string; + data?: { + status?: number; + }; + }; + + const errorDetails = getErrorMessage( apiError ); + yield controls.dispatch( 'core/notices', 'createErrorNotice', - sprintf( - // translators: %s Order id - __( - 'There has been an error canceling the payment for order #%s. Please try again later.', - 'woocommerce-payments' - ), - orderId - ) + `${ baseErrorMessage } ${ errorDetails }` ); } finally { yield controls.dispatch( diff --git a/client/data/authorizations/test/actions.test.ts b/client/data/authorizations/test/actions.test.ts index 1c73ab5d7a2..171ef6dd5ad 100644 --- a/client/data/authorizations/test/actions.test.ts +++ b/client/data/authorizations/test/actions.test.ts @@ -16,6 +16,7 @@ import { updateAuthorization, } from '../actions'; import authorizationsFixture from './authorizations.fixture.json'; +import { STORE_NAME } from 'wcpay/data/constants'; describe( 'Authorizations actions', () => { describe( 'submitCaptureAuthorization', () => { @@ -153,10 +154,117 @@ describe( 'Authorizations actions', () => { controls.dispatch( 'core/notices', 'createErrorNotice', - 'There has been an error capturing the payment for order #42. Please try again later.' + 'There has been an error capturing the payment for order #42. Unable to process the payment. Please try again later.' ) ); } ); + + describe( 'error handling', () => { + it( 'should create error notice with API error message', () => { + const generator = submitCaptureAuthorization( 'pi_123', 123 ); + + // Mock the start of the capture process + expect( generator.next().value ).toEqual( + controls.dispatch( + STORE_NAME, + 'startResolution', + 'getAuthorization', + [ 'pi_123' ] + ) + ); + + expect( generator.next().value ).toEqual( + controls.dispatch( + STORE_NAME, + 'setIsRequestingAuthorization', + true + ) + ); + + // Mock API error response + const apiError = { + code: 'wcpay_refunded_order_uncapturable', + message: + 'Payment cannot be captured for partially or fully refunded orders.', + data: { status: 400 }, + }; + + // Simulate API error + expect( generator.throw( apiError ).value ).toEqual( + controls.dispatch( + 'core/notices', + 'createErrorNotice', + 'There has been an error capturing the payment for order #123. Payment cannot be processed for partially or fully refunded orders.' + ) + ); + + // Verify cleanup in finally block + expect( generator.next().value ).toEqual( + controls.dispatch( + STORE_NAME, + 'finishResolution', + 'getAuthorization', + [ 'pi_123' ] + ) + ); + + expect( generator.next().value ).toEqual( + controls.dispatch( + STORE_NAME, + 'setIsRequestingAuthorization', + false + ) + ); + } ); + + it( 'should create error notice with fallback message when API error has no message', () => { + const generator = submitCaptureAuthorization( 'pi_123', 123 ); + + // Skip initial dispatch calls + generator.next(); + generator.next(); + + // Mock API error without message + const apiError = { + code: 'unknown_error', + data: { status: 500 }, + }; + + expect( generator.throw( apiError ).value ).toEqual( + controls.dispatch( + 'core/notices', + 'createErrorNotice', + 'There has been an error capturing the payment for order #123. Unable to process the payment. Please try again later.' + ) + ); + } ); + + it( 'should show default error notice for unknown error code', () => { + const generator = submitCaptureAuthorization( + 'pi_unknown', + 999 + ); + + // Start the generator to the point where it would throw an error + generator.next(); + generator.next(); + + // Mock an API error with an unknown error code + const apiError = { + code: 'unknown_error_code', + data: { status: 500 }, + }; + + // Expect the default error message to be dispatched + expect( generator.throw( apiError ).value ).toEqual( + controls.dispatch( + 'core/notices', + 'createErrorNotice', + 'There has been an error capturing the payment for order #999. Unable to process the payment. Please try again later.' + ) + ); + } ); + } ); } ); describe( 'submitCancelAuthorization', () => { @@ -294,9 +402,56 @@ describe( 'Authorizations actions', () => { controls.dispatch( 'core/notices', 'createErrorNotice', - 'There has been an error canceling the payment for order #42. Please try again later.' + 'There has been an error canceling the payment for order #42. Unable to process the payment. Please try again later.' ) ); } ); + + describe( 'error handling', () => { + it( 'should create error notice with API error message', () => { + const generator = submitCancelAuthorization( 'pi_123', 123 ); + + // Skip initial dispatch calls + generator.next(); + generator.next(); + + // Mock API error response + const apiError = { + code: 'wcpay_payment_uncapturable', + message: 'The payment cannot be canceled at this time.', + data: { status: 400 }, + }; + + expect( generator.throw( apiError ).value ).toEqual( + controls.dispatch( + 'core/notices', + 'createErrorNotice', + 'There has been an error canceling the payment for order #123. This payment cannot be processed in its current state.' + ) + ); + } ); + + it( 'should create error notice with fallback message when API error has no message', () => { + const generator = submitCancelAuthorization( 'pi_123', 123 ); + + // Skip initial dispatch calls + generator.next(); + generator.next(); + + // Mock API error without message + const apiError = { + code: 'unknown_error', + data: { status: 500 }, + }; + + expect( generator.throw( apiError ).value ).toEqual( + controls.dispatch( + 'core/notices', + 'createErrorNotice', + 'There has been an error canceling the payment for order #123. Unable to process the payment. Please try again later.' + ) + ); + } ); + } ); } ); } ); From 6639fe9fb7887e828ebc0386e6f4c536602d0e7d Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Wed, 18 Dec 2024 13:53:34 +0100 Subject: [PATCH 58/83] Set gateway method title when the title is defined (#9982) Co-authored-by: Timur Karimov --- changelog/fix-method-title-availability | 4 ++++ includes/class-wc-payment-gateway-wcpay.php | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelog/fix-method-title-availability diff --git a/changelog/fix-method-title-availability b/changelog/fix-method-title-availability new file mode 100644 index 00000000000..d9d2a0c0217 --- /dev/null +++ b/changelog/fix-method-title-availability @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Set payment method title once title is known. diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 3e02aa6acf5..4f579caf2b1 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -321,8 +321,7 @@ public function __construct( ]; if ( 'card' !== $this->stripe_id ) { - $this->id = self::GATEWAY_ID . '_' . $this->stripe_id; - $this->method_title = "WooPayments ($this->title)"; + $this->id = self::GATEWAY_ID . '_' . $this->stripe_id; } // Capabilities have different keys than the payment method ID's, @@ -366,7 +365,8 @@ public function __construct( * @return string */ public function get_title() { - $this->title = $this->payment_method->get_title(); + $this->title = $this->payment_method->get_title(); + $this->method_title = "WooPayments ($this->title)"; return parent::get_title(); } From efc8b586011b2f9c055c0ff121c9b2c8ba9900d3 Mon Sep 17 00:00:00 2001 From: Francesco Date: Wed, 18 Dec 2024 15:03:46 +0100 Subject: [PATCH 59/83] fix: tokenized cart subscription signup fee price (#9951) --- changelog/fix-tokenized-cart-subscription-signup-fee | 5 +++++ .../tokenized-express-checkout/transformers/wc-to-stripe.js | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-tokenized-cart-subscription-signup-fee diff --git a/changelog/fix-tokenized-cart-subscription-signup-fee b/changelog/fix-tokenized-cart-subscription-signup-fee new file mode 100644 index 00000000000..5abe9f0226b --- /dev/null +++ b/changelog/fix-tokenized-cart-subscription-signup-fee @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: fix: tokenized cart subscription signup fee price + + diff --git a/client/tokenized-express-checkout/transformers/wc-to-stripe.js b/client/tokenized-express-checkout/transformers/wc-to-stripe.js index 7c4c9c14822..6d9b39035ce 100644 --- a/client/tokenized-express-checkout/transformers/wc-to-stripe.js +++ b/client/tokenized-express-checkout/transformers/wc-to-stripe.js @@ -40,8 +40,8 @@ export const transformPrice = ( price, priceObject ) => { export const transformCartDataForDisplayItems = ( cartData ) => { const displayItems = cartData.items.map( ( item ) => ( { amount: transformPrice( - parseInt( item.prices.price, 10 ), - item.prices + parseInt( item.totals?.line_subtotal || item.prices.price, 10 ), + item.totals || item.prices ), name: [ item.name, From 8a7027fff46f3bc2944bdd044f9098bd807d0357 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Wed, 18 Dec 2024 16:11:32 +0100 Subject: [PATCH 60/83] Load checkout scripts when they are not previously loaded on checkout page (#9984) --- ...checkout-scripts-on-checkout-if-not-loaded | 4 +++ includes/class-wc-payments-checkout.php | 31 +++++++++++++++---- 2 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 changelog/load-checkout-scripts-on-checkout-if-not-loaded diff --git a/changelog/load-checkout-scripts-on-checkout-if-not-loaded b/changelog/load-checkout-scripts-on-checkout-if-not-loaded new file mode 100644 index 00000000000..4a684203a2e --- /dev/null +++ b/changelog/load-checkout-scripts-on-checkout-if-not-loaded @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Load checkout scripts when they are not previously loaded on checkout page. diff --git a/includes/class-wc-payments-checkout.php b/includes/class-wc-payments-checkout.php index ee7a161f3b1..44d92d10b23 100644 --- a/includes/class-wc-payments-checkout.php +++ b/includes/class-wc-payments-checkout.php @@ -103,6 +103,7 @@ public function init_hooks() { add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] ); add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts_for_zero_order_total' ], 11 ); + add_action( 'woocommerce_after_checkout_form', [ $this, 'maybe_load_checkout_scripts' ] ); } /** @@ -151,11 +152,18 @@ public function register_scripts_for_zero_order_total() { ! has_block( 'woocommerce/checkout' ) && ! wp_script_is( 'wcpay-upe-checkout', 'enqueued' ) ) { - WC_Payments::get_gateway()->tokenization_script(); - $script_handle = 'wcpay-upe-checkout'; - $js_object = 'wcpay_upe_config'; - wp_localize_script( $script_handle, $js_object, WC_Payments::get_wc_payments_checkout()->get_payment_fields_js_config() ); - wp_enqueue_script( $script_handle ); + $this->load_checkout_scripts(); + } + } + + /** + * Sometimes the filters can remove the payment gateway from the checkout page which results in the payment fields not being displayed. + * This could prevent loading of the payment fields (checkout) scripts. + * This function ensures that these scripts are loaded. + */ + public function maybe_load_checkout_scripts() { + if ( is_checkout() && ! wp_script_is( 'wcpay-upe-checkout', 'enqueued' ) ) { + $this->load_checkout_scripts(); } } @@ -416,7 +424,7 @@ function () use ( $prepared_customer_data ) { } ?> -
gateway = $this->gateway->wc_payments_get_payment_gateway_by_id( $payment_method_id ); } } + + /** + * Load the checkout scripts. + */ + private function load_checkout_scripts() { + WC_Payments::get_gateway()->tokenization_script(); + $script_handle = 'wcpay-upe-checkout'; + $js_object = 'wcpay_upe_config'; + wp_localize_script( $script_handle, $js_object, WC_Payments::get_wc_payments_checkout()->get_payment_fields_js_config() ); + wp_enqueue_script( $script_handle ); + } } From 5eaa046fa289b83943f5b595074cd1fc0da417a6 Mon Sep 17 00:00:00 2001 From: Miguel Gasca Date: Wed, 18 Dec 2024 16:31:39 +0100 Subject: [PATCH 61/83] Add failure reason to failed timeline events (#9980) --- ...lude-more-info-on-transaction-details-page | 4 ++ client/payment-details/timeline/map-events.js | 19 +++++-- client/payment-details/timeline/mappings.ts | 43 +++++++++++++++ .../timeline/test/__snapshots__/index.js.snap | 2 +- .../test/__snapshots__/map-events.js.snap | 2 +- .../timeline/test/map-events.js | 55 +++++++++++++++++++ 6 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page diff --git a/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page b/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page new file mode 100644 index 00000000000..daf90a1cd39 --- /dev/null +++ b/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Add failure reason to failed payments in the timeline. diff --git a/client/payment-details/timeline/map-events.js b/client/payment-details/timeline/map-events.js index da4e275105c..b76f659ac63 100644 --- a/client/payment-details/timeline/map-events.js +++ b/client/payment-details/timeline/map-events.js @@ -30,7 +30,7 @@ import { import { formatFee } from 'utils/fees'; import { getAdminUrl } from 'wcpay/utils'; import { ShieldIcon } from 'wcpay/icons'; -import { fraudOutcomeRulesetMapping } from './mappings'; +import { fraudOutcomeRulesetMapping, paymentFailureMapping } from './mappings'; /** * Creates a timeline item about a payment status change @@ -772,6 +772,10 @@ const mapEventToTimelineItems = ( event ) => { ), ]; case 'failed': + const paymentFailureMessage = + paymentFailureMapping[ event.reason ] || + paymentFailureMapping.default; + return [ getStatusChangeTimelineItem( event, @@ -779,11 +783,14 @@ const mapEventToTimelineItems = ( event ) => { ), getMainTimelineItem( event, - stringWithAmount( - /* translators: %s is a monetary amount */ - __( 'A payment of %s failed.', 'woocommerce-payments' ), - event.amount, - true + sprintf( + /* translators: %1$s is the payment amount, %2$s is the failure reason message */ + __( + 'A payment of %1$s failed: %2$s.', + 'woocommerce-payments' + ), + formatExplicitCurrency( event.amount, event.currency ), + paymentFailureMessage ), ), diff --git a/client/payment-details/timeline/mappings.ts b/client/payment-details/timeline/mappings.ts index 80d2aceaa98..affcb62063a 100644 --- a/client/payment-details/timeline/mappings.ts +++ b/client/payment-details/timeline/mappings.ts @@ -65,3 +65,46 @@ export const fraudOutcomeRulesetMapping = { ), }, }; + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const paymentFailureMapping = { + card_declined: __( + 'The card was declined by the bank', + 'woocommerce-payments' + ), + expired_card: __( 'The card has expired', 'woocommerce-payments' ), + incorrect_cvc: __( + 'The security code is incorrect', + 'woocommerce-payments' + ), + incorrect_number: __( + 'The card number is incorrect', + 'woocommerce-payments' + ), + incorrect_zip: __( 'The postal code is incorrect', 'woocommerce-payments' ), + invalid_cvc: __( 'The security code is invalid', 'woocommerce-payments' ), + invalid_expiry_month: __( + 'The expiration month is invalid', + 'woocommerce-payments' + ), + invalid_expiry_year: __( + 'The expiration year is invalid', + 'woocommerce-payments' + ), + invalid_number: __( 'The card number is invalid', 'woocommerce-payments' ), + processing_error: __( + 'An error occurred while processing the card', + 'woocommerce-payments' + ), + authentication_required: __( + 'The payment requires authentication', + 'woocommerce-payments' + ), + insufficient_funds: __( + 'The card has insufficient funds to complete the purchase', + 'woocommerce-payments' + ), + + // Default fallback + default: __( 'The payment was declined', 'woocommerce-payments' ), +}; diff --git a/client/payment-details/timeline/test/__snapshots__/index.js.snap b/client/payment-details/timeline/test/__snapshots__/index.js.snap index 95f1e8a5c9f..c7915e6aee9 100644 --- a/client/payment-details/timeline/test/__snapshots__/index.js.snap +++ b/client/payment-details/timeline/test/__snapshots__/index.js.snap @@ -933,7 +933,7 @@ exports[`PaymentDetailsTimeline renders correctly (with a mocked Timeline compon - A payment of $77.00 failed. + A payment of $77.00 failed: The card was declined by the bank.
, diff --git a/client/payment-details/timeline/test/map-events.js b/client/payment-details/timeline/test/map-events.js index c3e42ceae8b..f1c0588d659 100644 --- a/client/payment-details/timeline/test/map-events.js +++ b/client/payment-details/timeline/test/map-events.js @@ -662,4 +662,59 @@ describe( 'mapTimelineEvents', () => { ).toMatchSnapshot(); } ); } ); + + test( 'formats payment failure events with different error codes', () => { + const testCases = [ + { + reason: 'insufficient_funds', + expectedMessage: + 'A payment of $77.00 USD failed: The card has insufficient funds to complete the purchase.', + }, + { + reason: 'expired_card', + expectedMessage: + 'A payment of $77.00 USD failed: The card has expired.', + }, + { + reason: 'invalid_cvc', + expectedMessage: + 'A payment of $77.00 USD failed: The security code is invalid.', + }, + { + reason: 'unknown_reason', + expectedMessage: + 'A payment of $77.00 USD failed: The payment was declined.', + }, + ]; + + testCases.forEach( ( { reason, expectedMessage } ) => { + const events = mapTimelineEvents( [ + { + amount: 7700, + currency: 'USD', + datetime: 1585712113, + reason, + type: 'failed', + }, + ] ); + + expect( events[ 1 ].headline ).toBe( expectedMessage ); + } ); + } ); + + test( 'formats payment failure events with different currencies', () => { + const events = mapTimelineEvents( [ + { + amount: 7700, + currency: 'EUR', + datetime: 1585712113, + reason: 'card_declined', + type: 'failed', + }, + ] ); + + expect( events[ 1 ].headline ).toBe( + 'A payment of €77.00 EUR failed: The card was declined by the bank.' + ); + } ); } ); From f1a21c81e18ae6a3ef8ba6439b74ace7d6261df4 Mon Sep 17 00:00:00 2001 From: Radoslav Georgiev Date: Thu, 19 Dec 2024 09:06:59 +0200 Subject: [PATCH 62/83] Fix rounding problems with Level 3 data (#9991) --- changelog/fix-9114-level3-rounding | 4 ++ src/Internal/Service/Level3Service.php | 37 +++++++++++++++---- .../Internal/Service/Level3ServiceTest.php | 23 ++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 changelog/fix-9114-level3-rounding diff --git a/changelog/fix-9114-level3-rounding b/changelog/fix-9114-level3-rounding new file mode 100644 index 00000000000..713c8d684cc --- /dev/null +++ b/changelog/fix-9114-level3-rounding @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Add a rounding entry to Level 3 data for rare cases where rounding errors break calculations. diff --git a/src/Internal/Service/Level3Service.php b/src/Internal/Service/Level3Service.php index 67db748debe..b75f3dd1271 100644 --- a/src/Internal/Service/Level3Service.php +++ b/src/Internal/Service/Level3Service.php @@ -80,10 +80,10 @@ public function get_data_from_order( int $order_id ): array { $order_items = array_values( $order->get_items( [ 'line_item', 'fee' ] ) ); $currency = $order->get_currency(); - $process_item = function ( $item ) use ( $currency ) { - return $this->process_item( $item, $currency ); - }; - $items_to_send = array_map( $process_item, $order_items ); + $items_to_send = []; + foreach ( $order_items as $item ) { + $items_to_send = array_merge( $items_to_send, $this->process_item( $item, $currency ) ); + } $level3_data = [ 'merchant_reference' => (string) $order->get_id(), // An alphanumeric string of up to characters in length. This unique value is assigned by the merchant to identify the order. Also known as an “Order ID”. @@ -137,9 +137,9 @@ public function get_data_from_order( int $order_id ): array { * * @param WC_Order_Item_Product|WC_Order_Item_Fee $item Item to process. * @param string $currency Currency to use. - * @return \stdClass + * @return \stdClass[] */ - private function process_item( WC_Order_Item $item, string $currency ): stdClass { + private function process_item( WC_Order_Item $item, string $currency ): array { // Check to see if it is a WC_Order_Item_Product or a WC_Order_Item_Fee. if ( $item instanceof WC_Order_Item_Product ) { $subtotal = $item->get_subtotal(); @@ -164,7 +164,7 @@ private function process_item( WC_Order_Item $item, string $currency ): stdClass $unit_cost = 0; } - return (object) [ + $line_item = (object) [ 'product_code' => (string) $product_code, // Up to 12 characters that uniquely identify the product. 'product_description' => $description, // Up to 26 characters long describing the product. 'unit_cost' => $unit_cost, // Cost of the product, in cents, as a non-negative integer. @@ -172,6 +172,29 @@ private function process_item( WC_Order_Item $item, string $currency ): stdClass 'tax_amount' => $tax_amount, // The amount of tax this item had added to it, in cents, as a non-negative integer. 'discount_amount' => $discount_amount, // The amount an item was discounted—if there was a sale,for example, as a non-negative integer. ]; + $line_items = [ $line_item ]; + + /** + * In edge cases, rounding after division might lead to a slight inconsistency. + * + * For example: 10/3 with 2 decimal places = 3.33, but 3.33*3 = 9.99. + */ + if ( $subtotal > 0 ) { + $prepared_subtotal = $this->prepare_amount( $subtotal, $currency ); + $difference = $prepared_subtotal - ( $unit_cost * $quantity ); + if ( $difference > 0 ) { + $line_items[] = (object) [ + 'product_code' => 'rounding-fix', + 'product_description' => __( 'Rounding fix', 'woocommerce-payments' ), + 'unit_cost' => $difference, + 'quantity' => 1, + 'tax_amount' => 0, + 'discount_amount' => 0, + ]; + } + } + + return $line_items; } /** diff --git a/tests/unit/src/Internal/Service/Level3ServiceTest.php b/tests/unit/src/Internal/Service/Level3ServiceTest.php index fe3eee573c3..dba9766386d 100644 --- a/tests/unit/src/Internal/Service/Level3ServiceTest.php +++ b/tests/unit/src/Internal/Service/Level3ServiceTest.php @@ -167,6 +167,10 @@ protected function mock_level_3_order( $mock_items = array_merge( $mock_items, array_fill( 0, $basket_size - count( $mock_items ), $mock_items[0] ) ); } + $this->mock_order( $mock_items, $shipping_postcode ); + } + + protected function mock_order( array $mock_items, string $shipping_postcode ) { // Setup the order. $mock_order = $this ->getMockBuilder( WC_Order::class ) @@ -434,6 +438,25 @@ public function test_full_level3_data_with_float_quantity() { $this->assertEquals( $expected_data, $level_3_data ); } + public function test_rounding_in_edge_cases() { + $this->mock_account->method( 'get_account_country' )->willReturn( Country_Code::UNITED_STATES ); + + $mock_items = []; + $mock_items[] = $this->create_mock_item( 'Beanie with Addon', 3, 73, 0, 30 ); + $this->mock_order( $mock_items, '98012' ); + + $level_3_data = $this->sut->get_data_from_order( $this->order_id ); + + $this->assertCount( 2, $level_3_data['line_items'] ); + $this->assertEquals( 2433, $level_3_data['line_items'][0]->unit_cost ); + $this->assertEquals( 'rounding-fix', $level_3_data['line_items'][1]->product_code ); + $this->assertEquals( 'Rounding fix', $level_3_data['line_items'][1]->product_description ); + $this->assertEquals( 1, $level_3_data['line_items'][1]->unit_cost ); + $this->assertEquals( 1, $level_3_data['line_items'][1]->quantity ); + $this->assertEquals( 0, $level_3_data['line_items'][1]->tax_amount ); + $this->assertEquals( 0, $level_3_data['line_items'][1]->discount_amount ); + } + public function test_full_level3_data_with_float_quantity_zero() { $expected_data = [ 'merchant_reference' => '210', From 7b6e7efa8aac9cde89a8399a158561173497560e Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 19 Dec 2024 09:21:00 +0100 Subject: [PATCH 63/83] Remove hooks from customer service constructor (#9992) --- .../dev-7264-remove-hooks-from-customer-service-constructor | 4 ++++ dev/phpcs/WCPay/ruleset.xml | 4 ---- includes/class-wc-payments-customer-service.php | 5 +++++ includes/class-wc-payments-token-service.php | 5 +++++ includes/class-wc-payments.php | 2 ++ 5 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 changelog/dev-7264-remove-hooks-from-customer-service-constructor diff --git a/changelog/dev-7264-remove-hooks-from-customer-service-constructor b/changelog/dev-7264-remove-hooks-from-customer-service-constructor new file mode 100644 index 00000000000..d912717fc31 --- /dev/null +++ b/changelog/dev-7264-remove-hooks-from-customer-service-constructor @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Remove hooks from customer and token services to dedicated methods diff --git a/dev/phpcs/WCPay/ruleset.xml b/dev/phpcs/WCPay/ruleset.xml index 9806ccfe9e7..7c8cefbd0e3 100644 --- a/dev/phpcs/WCPay/ruleset.xml +++ b/dev/phpcs/WCPay/ruleset.xml @@ -17,10 +17,6 @@ */includes/class-wc-payments-order-success-page.php - - */includes/class-wc-payments-customer-service.php - */includes/class-wc-payments-token-service.php - */includes/class-wc-payments-webhook-reliability-service.php diff --git a/includes/class-wc-payments-customer-service.php b/includes/class-wc-payments-customer-service.php index 05f95c32d31..d0f97e061c0 100644 --- a/includes/class-wc-payments-customer-service.php +++ b/includes/class-wc-payments-customer-service.php @@ -99,7 +99,12 @@ public function __construct( $this->database_cache = $database_cache; $this->session_service = $session_service; $this->order_service = $order_service; + } + /** + * Initialize hooks + */ + public function init_hooks() { /* * Adds the WooCommerce Payments customer ID found in the user session * to the WordPress user as metadata. diff --git a/includes/class-wc-payments-token-service.php b/includes/class-wc-payments-token-service.php index 7bfdc482e18..283a0d7851a 100644 --- a/includes/class-wc-payments-token-service.php +++ b/includes/class-wc-payments-token-service.php @@ -47,7 +47,12 @@ class WC_Payments_Token_Service { public function __construct( WC_Payments_API_Client $payments_api_client, WC_Payments_Customer_Service $customer_service ) { $this->payments_api_client = $payments_api_client; $this->customer_service = $customer_service; + } + /** + * Initializes hooks. + */ + public function init_hooks() { add_action( 'woocommerce_payment_token_deleted', [ $this, 'woocommerce_payment_token_deleted' ], 10, 2 ); add_action( 'woocommerce_payment_token_set_default', [ $this, 'woocommerce_payment_token_set_default' ], 10, 2 ); add_filter( 'woocommerce_get_customer_payment_tokens', [ $this, 'woocommerce_get_customer_payment_tokens' ], 10, 3 ); diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 17300478794..32c75df5fc7 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -554,6 +554,8 @@ public static function init() { self::$onboarding_service->init_hooks(); self::$incentives_service->init_hooks(); self::$compatibility_service->init_hooks(); + self::$customer_service->init_hooks(); + self::$token_service->init_hooks(); $payment_method_classes = [ CC_Payment_Method::class, From 5005b6bcc01664f1df277dac599c111227b424ad Mon Sep 17 00:00:00 2001 From: Dat Hoang Date: Thu, 19 Dec 2024 15:58:18 +0700 Subject: [PATCH 64/83] Add seller_message to failed order notes (#9934) --- changelog/feat-9810-add-seller-message | 4 ++ includes/class-wc-payment-gateway-wcpay.php | 17 ++++++- includes/class-wc-payments.php | 1 + ...wc-payment-gateway-wcpay-subscriptions.php | 8 ++- .../class-api-merchant-exception.php | 49 +++++++++++++++++++ .../class-wc-payments-api-client.php | 8 +++ .../test-class-wc-payments-api-client.php | 20 ++++++++ 7 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 changelog/feat-9810-add-seller-message create mode 100644 includes/exceptions/class-api-merchant-exception.php diff --git a/changelog/feat-9810-add-seller-message b/changelog/feat-9810-add-seller-message new file mode 100644 index 00000000000..2669c24015b --- /dev/null +++ b/changelog/feat-9810-add-seller-message @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add seller_message to failed order notes diff --git a/includes/class-wc-payment-gateway-wcpay.php b/includes/class-wc-payment-gateway-wcpay.php index 4f579caf2b1..d1be21241b9 100644 --- a/includes/class-wc-payment-gateway-wcpay.php +++ b/includes/class-wc-payment-gateway-wcpay.php @@ -20,7 +20,19 @@ use WCPay\Constants\Intent_Status; use WCPay\Constants\Payment_Type; use WCPay\Constants\Payment_Method; -use WCPay\Exceptions\{ Add_Payment_Method_Exception, Amount_Too_Small_Exception, Process_Payment_Exception, Intent_Authentication_Exception, API_Exception, Invalid_Address_Exception, Fraud_Prevention_Enabled_Exception, Invalid_Phone_Number_Exception, Rate_Limiter_Enabled_Exception, Order_ID_Mismatch_Exception, Order_Not_Found_Exception, New_Process_Payment_Exception }; +use WCPay\Exceptions\{Add_Payment_Method_Exception, + Amount_Too_Small_Exception, + API_Merchant_Exception, + Process_Payment_Exception, + Intent_Authentication_Exception, + API_Exception, + Invalid_Address_Exception, + Fraud_Prevention_Enabled_Exception, + Invalid_Phone_Number_Exception, + Rate_Limiter_Enabled_Exception, + Order_ID_Mismatch_Exception, + Order_Not_Found_Exception, + New_Process_Payment_Exception}; use WCPay\Core\Server\Request\Cancel_Intention; use WCPay\Core\Server\Request\Capture_Intention; use WCPay\Core\Server\Request\Create_And_Confirm_Intention; @@ -1270,6 +1282,9 @@ public function process_payment( $order_id ) { ); $error_details = esc_html( rtrim( $e->getMessage(), '.' ) ); + if ( $e instanceof API_Merchant_Exception ) { + $error_details = $error_details . '. ' . esc_html( rtrim( $e->get_merchant_message(), '.' ) ); + } if ( $e instanceof API_Exception && 'card_error' === $e->get_error_type() ) { // If the payment failed with a 'card_error' API exception, initialize the fraud meta box diff --git a/includes/class-wc-payments.php b/includes/class-wc-payments.php index 32c75df5fc7..4ad2d32625e 100644 --- a/includes/class-wc-payments.php +++ b/includes/class-wc-payments.php @@ -354,6 +354,7 @@ public static function init() { include_once __DIR__ . '/exceptions/class-base-exception.php'; include_once __DIR__ . '/exceptions/class-api-exception.php'; + include_once __DIR__ . '/exceptions/class-api-merchant-exception.php'; include_once __DIR__ . '/exceptions/class-connection-exception.php'; include_once __DIR__ . '/core/class-mode.php'; diff --git a/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php b/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php index 31ec70bedf8..d2584f9b824 100644 --- a/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php +++ b/includes/compat/subscriptions/trait-wc-payment-gateway-wcpay-subscriptions.php @@ -11,6 +11,7 @@ use WCPay\Core\Server\Request\Get_Intention; use WCPay\Exceptions\API_Exception; +use WCPay\Exceptions\API_Merchant_Exception; use WCPay\Exceptions\Invalid_Payment_Method_Exception; use WCPay\Exceptions\Add_Payment_Method_Exception; use WCPay\Exceptions\Order_Not_Found_Exception; @@ -342,6 +343,11 @@ public function scheduled_subscription_payment( $amount, $renewal_order ) { $renewal_order->update_status( 'failed' ); if ( ! empty( $payment_information ) ) { + $error_details = esc_html( rtrim( $e->getMessage(), '.' ) ); + if ( $e instanceof API_Merchant_Exception ) { + $error_details = $error_details . '. ' . esc_html( rtrim( $e->get_merchant_message(), '.' ) ); + } + $note = sprintf( WC_Payments_Utils::esc_interpolated_html( /* translators: %1: the failed payment amount, %2: error message */ @@ -358,7 +364,7 @@ public function scheduled_subscription_payment( $amount, $renewal_order ) { wc_price( $amount, [ 'currency' => WC_Payments_Utils::get_order_intent_currency( $renewal_order ) ] ), $renewal_order ), - esc_html( rtrim( $e->getMessage(), '.' ) ) + $error_details ); $renewal_order->add_order_note( $note ); } diff --git a/includes/exceptions/class-api-merchant-exception.php b/includes/exceptions/class-api-merchant-exception.php new file mode 100644 index 00000000000..ac10bd271bc --- /dev/null +++ b/includes/exceptions/class-api-merchant-exception.php @@ -0,0 +1,49 @@ +merchant_message = $merchant_message; + + parent::__construct( $message, $error_code, $http_code, $error_type, $decline_code, $code, $previous ); + } + + /** + * Returns the merchant message. + * + * @return string Merchant message. + */ + public function get_merchant_message(): string { + return $this->merchant_message; + } +} 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 b3adf5bf7eb..e90094d57de 100644 --- a/includes/wc-payment-api/class-wc-payments-api-client.php +++ b/includes/wc-payment-api/class-wc-payments-api-client.php @@ -9,6 +9,7 @@ use WCPay\Constants\Intent_Status; use WCPay\Exceptions\API_Exception; +use WCPay\Exceptions\API_Merchant_Exception; use WCPay\Exceptions\Amount_Too_Small_Exception; use WCPay\Exceptions\Amount_Too_Large_Exception; use WCPay\Exceptions\Connection_Exception; @@ -2419,6 +2420,13 @@ protected function check_response_for_errors( $response ) { ); Logger::error( "$error_message ($error_code)" ); + + if ( 'card_declined' === $error_code && isset( $response_body['error']['payment_intent']['charges']['data'][0]['outcome']['seller_message'] ) ) { + $merchant_message = $response_body['error']['payment_intent']['charges']['data'][0]['outcome']['seller_message']; + + throw new API_Merchant_Exception( $message, $error_code, $response_code, $merchant_message, $error_type, $decline_code ); + } + throw new API_Exception( $message, $error_code, $response_code, $error_type, $decline_code ); } } 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 3fc4a56c8f6..fb95bcf1591 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 @@ -7,7 +7,9 @@ use WCPay\Constants\Country_Code; use WCPay\Constants\Intent_Status; +use WCPay\Core\Server\Request\Create_And_Confirm_Intention; use WCPay\Exceptions\API_Exception; +use WCPay\Exceptions\API_Merchant_Exception; use WCPay\Internal\Logger; use WCPay\Exceptions\Connection_Exception; use WCPay\Fraud_Prevention\Fraud_Prevention_Service; @@ -1195,6 +1197,24 @@ public function test_get_tracking_info() { $this->assertEquals( $expect, $result ); } + public function test_throws_api_merchant_exception() { + $mock_response = []; + $mock_response['error']['code'] = 'card_declined'; + $mock_response['error']['payment_intent']['charges']['data'][0]['outcome']['seller_message'] = 'Bank declined'; + $this->set_http_mock_response( + 401, + $mock_response + ); + + try { + // This is a dummy call to trigger the response so that our test can validate the exception. + $this->payments_api_client->create_subscription(); + } catch ( API_Merchant_Exception $e ) { + $this->assertSame( 'card_declined', $e->get_error_code() ); + $this->assertSame( 'Bank declined', $e->get_merchant_message() ); + } + } + /** * Set up http mock response. * From a64815337185fff3d801562c43401715e34de8f6 Mon Sep 17 00:00:00 2001 From: Jessy Pappachan <32092402+jessy-p@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:10:20 +0530 Subject: [PATCH 65/83] Use the filter param in server-side disputes CSV export (#9988) Co-authored-by: Jessy Co-authored-by: Nagesh Pai --- changelog/fix-9987-filter-csv-disputes | 4 ++++ client/data/disputes/hooks.ts | 15 ++------------- client/data/disputes/resolvers.js | 7 +++++-- client/disputes/index.tsx | 2 ++ 4 files changed, 13 insertions(+), 15 deletions(-) create mode 100644 changelog/fix-9987-filter-csv-disputes diff --git a/changelog/fix-9987-filter-csv-disputes b/changelog/fix-9987-filter-csv-disputes new file mode 100644 index 00000000000..e4a87b24b1b --- /dev/null +++ b/changelog/fix-9987-filter-csv-disputes @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix filtering in async Disputes CSV export diff --git a/client/data/disputes/hooks.ts b/client/data/disputes/hooks.ts index b8a95b1e5e6..5db1c2c3c59 100644 --- a/client/data/disputes/hooks.ts +++ b/client/data/disputes/hooks.ts @@ -17,7 +17,6 @@ import type { } from 'wcpay/types/disputes'; import type { ApiError } from 'wcpay/types/errors'; import { STORE_NAME } from '../constants'; -import { disputeAwaitingResponseStatuses } from 'wcpay/disputes/filters/config'; /** * Returns the dispute object, error object, and loading state. @@ -98,11 +97,6 @@ export const useDisputes = ( { ( select ) => { const { getDisputes, isResolving } = select( STORE_NAME ); - const search = - filter === 'awaiting_response' - ? disputeAwaitingResponseStatuses - : undefined; - const query = { paged: Number.isNaN( parseInt( paged ?? '', 10 ) ) ? '1' @@ -119,7 +113,7 @@ export const useDisputes = ( { dateBetween.sort( ( a, b ) => moment( a ).diff( moment( b ) ) ), - search, + filter, statusIs, statusIsNot, orderBy: orderBy || 'created', @@ -163,11 +157,6 @@ export const useDisputesSummary = ( { ( select ) => { const { getDisputesSummary, isResolving } = select( STORE_NAME ); - const search = - filter === 'awaiting_response' - ? disputeAwaitingResponseStatuses - : undefined; - const query = { paged: Number.isNaN( parseInt( paged ?? '', 10 ) ) ? '1' @@ -180,7 +169,7 @@ export const useDisputesSummary = ( { dateBefore, dateAfter, dateBetween, - search, + filter, statusIs, statusIsNot, }; diff --git a/client/data/disputes/resolvers.js b/client/data/disputes/resolvers.js index bf45770537c..ce748a46562 100644 --- a/client/data/disputes/resolvers.js +++ b/client/data/disputes/resolvers.js @@ -20,6 +20,7 @@ import { updateDisputesSummary, updateErrorForDispute, } from './actions'; +import { disputeAwaitingResponseStatuses } from 'wcpay/disputes/filters/config'; const formatQueryFilters = ( query ) => ( { user_email: query.userEmail, @@ -31,7 +32,10 @@ const formatQueryFilters = ( query ) => ( { formatDateValue( query.dateBetween[ 0 ] ), formatDateValue( query.dateBetween[ 1 ], true ), ], - search: query.search, + search: + query.filter === 'awaiting_response' + ? disputeAwaitingResponseStatuses + : query.search, status_is: query.statusIs, status_is_not: query.statusIsNot, locale: query.locale, @@ -42,7 +46,6 @@ export function getDisputesCSV( query ) { `${ NAMESPACE }/disputes/download`, formatQueryFilters( query ) ); - return path; } diff --git a/client/disputes/index.tsx b/client/disputes/index.tsx index 060afccce35..cdb85131f5d 100644 --- a/client/disputes/index.tsx +++ b/client/disputes/index.tsx @@ -372,6 +372,7 @@ export const DisputesList = (): JSX.Element => { date_after: dateAfter, date_between: dateBetween, match, + filter, status_is: statusIs, status_is_not: statusIsNot, } = getQuery(); @@ -407,6 +408,7 @@ export const DisputesList = (): JSX.Element => { dateBefore, dateBetween, match, + filter, statusIs, statusIsNot, } ), From 771e3abe2a3145d32c493a43eb78d83df1d4831c Mon Sep 17 00:00:00 2001 From: Cvetan Cvetanov Date: Thu, 19 Dec 2024 12:45:15 +0200 Subject: [PATCH 66/83] Enhance Embedded Components and MOX to support custom width and paddings (#9995) Co-authored-by: Dan Paun <82826872+dpaun1985@users.noreply.github.com> --- changelog/update-9919-embedded-components-width | 4 ++++ client/onboarding/style.scss | 8 +++++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 changelog/update-9919-embedded-components-width diff --git a/changelog/update-9919-embedded-components-width b/changelog/update-9919-embedded-components-width new file mode 100644 index 00000000000..ca8fe89ebb7 --- /dev/null +++ b/changelog/update-9919-embedded-components-width @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Update Embedded Components and MOX to support custom width and paddings. diff --git a/client/onboarding/style.scss b/client/onboarding/style.scss index 67a6351aab6..415849c1d06 100644 --- a/client/onboarding/style.scss +++ b/client/onboarding/style.scss @@ -85,10 +85,12 @@ body.wcpay-onboarding__body { } &__content { - max-width: 400px; + max-width: 615px; + width: 100%; - @media screen and ( min-width: $break-mobile ) { - width: 400px; + @media screen and ( max-width: $break-mobile ) { + width: 100%; + padding: 0 $gap; } } From e61393136fb409c73802b0ee68ce49ccdf6968e7 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Thu, 19 Dec 2024 15:46:50 +0100 Subject: [PATCH 67/83] Hide transaction fee on admin order view screen when transaction is not captured (#9997) --- ...418-hide-transaction-fees-when-transaction-is-not-captured | 4 ++++ includes/admin/class-wc-payments-admin.php | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured diff --git a/changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured b/changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured new file mode 100644 index 00000000000..f524fd812f1 --- /dev/null +++ b/changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Hide transaction fee on admin view order screen when transaction is not captured. diff --git a/includes/admin/class-wc-payments-admin.php b/includes/admin/class-wc-payments-admin.php index e7ad01fe210..d78671d1298 100644 --- a/includes/admin/class-wc-payments-admin.php +++ b/includes/admin/class-wc-payments-admin.php @@ -6,6 +6,7 @@ */ use Automattic\Jetpack\Identity_Crisis as Jetpack_Identity_Crisis; +use WCPay\Constants\Intent_Status; use WCPay\Core\Server\Request; use WCPay\Database_Cache; use WCPay\Logger; @@ -1253,7 +1254,7 @@ public function show_woopay_payment_method_name_admin( $order_id ) { */ public function display_wcpay_transaction_fee( $order_id ) { $order = wc_get_order( $order_id ); - if ( ! $order || ! $order->get_meta( '_wcpay_transaction_fee' ) ) { + if ( ! $order || ! $order->get_meta( '_wcpay_transaction_fee' ) || Intent_Status::REQUIRES_CAPTURE === $order->get_meta( WC_Payments_Order_Service::INTENTION_STATUS_META_KEY ) ) { return; } ?> From 7b223259e070bf7ac54d0e9e0715567b013b42f9 Mon Sep 17 00:00:00 2001 From: Francesco Date: Thu, 19 Dec 2024 15:55:08 +0100 Subject: [PATCH 68/83] fix: tokenized ECE item compatibility w/ product bundles (#9994) --- .../fix-tokenized-ece-product-bundles-totals | 5 + .../__tests__/wc-product-bundles.test.js | 253 ++++++++++++++++++ .../compatibility/wc-product-bundles.js | 19 ++ client/tokenized-express-checkout/index.js | 1 + .../transformers/wc-to-stripe.js | 11 +- 5 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 changelog/fix-tokenized-ece-product-bundles-totals create mode 100644 client/tokenized-express-checkout/compatibility/__tests__/wc-product-bundles.test.js create mode 100644 client/tokenized-express-checkout/compatibility/wc-product-bundles.js diff --git a/changelog/fix-tokenized-ece-product-bundles-totals b/changelog/fix-tokenized-ece-product-bundles-totals new file mode 100644 index 00000000000..c003feec46a --- /dev/null +++ b/changelog/fix-tokenized-ece-product-bundles-totals @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: fix: tokenized ECE item compatibility w/ product bundles + + diff --git a/client/tokenized-express-checkout/compatibility/__tests__/wc-product-bundles.test.js b/client/tokenized-express-checkout/compatibility/__tests__/wc-product-bundles.test.js new file mode 100644 index 00000000000..91009ab1a25 --- /dev/null +++ b/client/tokenized-express-checkout/compatibility/__tests__/wc-product-bundles.test.js @@ -0,0 +1,253 @@ +/** + * External dependencies + */ +import { applyFilters } from '@wordpress/hooks'; + +/** + * Internal dependencies + */ +import '../wc-product-bundles'; + +describe( 'ECE product bundles compatibility', () => { + it( 'filters out cart items that are bundled by something else', () => { + const cartData = applyFilters( + 'wcpay.express-checkout.map-line-items', + { + items: [ + { + key: 'd179a6924eafc82d7864f1e0caedbe95', + id: 261, + type: 'bundle', + quantity: 1, + item_data: [ + { + key: 'Includes', + value: 'T-Shirt × 1', + }, + { + key: 'Includes', + value: 'T-Shirt with Logo × 2', + }, + { + key: 'Includes', + value: 'V-Neck T-Shirt - Medium × 1', + }, + ], + extensions: { + bundles: { + bundled_items: [ + 'abda15f782e68dc63bd615d6a05fa3d2', + '4d16fa6ebc10a1d66013b0f85640eb2b', + 'ff279cc5574ef1cf45aa76bde0d66baa', + ], + bundle_data: { + configuration: { + '1': { + product_id: 13, + quantity: 1, + discount: 20, + optional_selected: 'yes', + }, + '2': { + product_id: 30, + quantity: 2, + discount: '', + }, + '3': { + product_id: 10, + quantity: 1, + discount: '', + attributes: { + attribute_size: 'Medium', + }, + variation_id: '25', + }, + '4': { + product_id: 10, + quantity: 0, + discount: '', + optional_selected: 'no', + attributes: [], + }, + }, + is_editable: false, + is_price_hidden: false, + is_subtotal_hidden: false, + is_hidden: false, + is_meta_hidden_in_cart: true, + is_meta_hidden_in_summary: false, + }, + }, + }, + }, + { + key: 'abda15f782e68dc63bd615d6a05fa3d2', + id: 13, + type: 'simple', + quantity: 1, + extensions: { + bundles: { + bundled_by: 'd179a6924eafc82d7864f1e0caedbe95', + bundled_item_data: { + bundle_id: 261, + bundled_item_id: 1, + is_removable: true, + is_indented: true, + is_subtotal_aggregated: true, + is_parent_visible: true, + is_last: false, + is_price_hidden: false, + is_subtotal_hidden: false, + is_thumbnail_hidden: false, + is_hidden_in_cart: false, + is_hidden_in_summary: true, + }, + }, + }, + }, + { + key: '4d16fa6ebc10a1d66013b0f85640eb2b', + id: 30, + type: 'simple', + quantity: 2, + extensions: { + bundles: { + bundled_by: 'd179a6924eafc82d7864f1e0caedbe95', + bundled_item_data: { + bundle_id: 261, + bundled_item_id: 2, + is_removable: false, + is_indented: true, + is_subtotal_aggregated: true, + is_parent_visible: true, + is_last: false, + is_price_hidden: true, + is_subtotal_hidden: true, + is_thumbnail_hidden: false, + is_hidden_in_cart: false, + is_hidden_in_summary: true, + }, + }, + }, + }, + { + key: 'ff279cc5574ef1cf45aa76bde0d66baa', + id: 25, + type: 'variation', + quantity: 1, + extensions: { + bundles: { + bundled_by: 'd179a6924eafc82d7864f1e0caedbe95', + bundled_item_data: { + bundle_id: 261, + bundled_item_id: 3, + is_removable: false, + is_indented: true, + is_subtotal_aggregated: true, + is_parent_visible: true, + is_last: true, + is_price_hidden: true, + is_subtotal_hidden: true, + is_thumbnail_hidden: false, + is_hidden_in_cart: false, + is_hidden_in_summary: true, + }, + }, + }, + }, + { + key: 'c51ce410c124a10e0db5e4b97fc2af39', + id: 13, + type: 'simple', + quantity: 1, + extensions: { + bundles: [], + }, + }, + ], + items_count: 2, + } + ); + + expect( cartData ).toStrictEqual( { + items: [ + { + extensions: { + bundles: { + bundle_data: { + configuration: { + '1': { + discount: 20, + optional_selected: 'yes', + product_id: 13, + quantity: 1, + }, + '2': { + discount: '', + product_id: 30, + quantity: 2, + }, + '3': { + attributes: { + attribute_size: 'Medium', + }, + discount: '', + product_id: 10, + quantity: 1, + variation_id: '25', + }, + '4': { + attributes: [], + discount: '', + optional_selected: 'no', + product_id: 10, + quantity: 0, + }, + }, + is_editable: false, + is_hidden: false, + is_meta_hidden_in_cart: true, + is_meta_hidden_in_summary: false, + is_price_hidden: false, + is_subtotal_hidden: false, + }, + bundled_items: [ + 'abda15f782e68dc63bd615d6a05fa3d2', + '4d16fa6ebc10a1d66013b0f85640eb2b', + 'ff279cc5574ef1cf45aa76bde0d66baa', + ], + }, + }, + id: 261, + item_data: [ + { + key: 'Includes', + value: 'T-Shirt × 1', + }, + { + key: 'Includes', + value: 'T-Shirt with Logo × 2', + }, + { + key: 'Includes', + value: 'V-Neck T-Shirt - Medium × 1', + }, + ], + key: 'd179a6924eafc82d7864f1e0caedbe95', + quantity: 1, + type: 'bundle', + }, + { + extensions: { + bundles: [], + }, + id: 13, + key: 'c51ce410c124a10e0db5e4b97fc2af39', + quantity: 1, + type: 'simple', + }, + ], + items_count: 2, + } ); + } ); +} ); diff --git a/client/tokenized-express-checkout/compatibility/wc-product-bundles.js b/client/tokenized-express-checkout/compatibility/wc-product-bundles.js new file mode 100644 index 00000000000..7a3d2a4dc3c --- /dev/null +++ b/client/tokenized-express-checkout/compatibility/wc-product-bundles.js @@ -0,0 +1,19 @@ +/** + * External dependencies + */ +import { addFilter } from '@wordpress/hooks'; + +addFilter( + 'wcpay.express-checkout.map-line-items', + 'automattic/wcpay/express-checkout', + ( cartData ) => { + return { + ...cartData, + // ensuring that the items that are bundled by another don't appear in the summary. + // otherwise they might be contributing to the wrong order total, creating errors. + items: cartData.items.filter( + ( item ) => ! item.extensions?.bundles?.bundled_by + ), + }; + } +); diff --git a/client/tokenized-express-checkout/index.js b/client/tokenized-express-checkout/index.js index 3b2eb1a8b46..e6c24d02e91 100644 --- a/client/tokenized-express-checkout/index.js +++ b/client/tokenized-express-checkout/index.js @@ -13,6 +13,7 @@ import '../checkout/express-checkout-buttons.scss'; import './compatibility/wc-deposits'; import './compatibility/wc-order-attribution'; import './compatibility/wc-product-page'; +import './compatibility/wc-product-bundles'; import { getExpressCheckoutButtonAppearance, getExpressCheckoutButtonStyleSettings, diff --git a/client/tokenized-express-checkout/transformers/wc-to-stripe.js b/client/tokenized-express-checkout/transformers/wc-to-stripe.js index 6d9b39035ce..794fc5309b1 100644 --- a/client/tokenized-express-checkout/transformers/wc-to-stripe.js +++ b/client/tokenized-express-checkout/transformers/wc-to-stripe.js @@ -8,6 +8,7 @@ import { decodeEntities } from '@wordpress/html-entities'; * Internal dependencies */ import { getExpressCheckoutData } from '../utils'; +import { applyFilters } from '@wordpress/hooks'; /** * GooglePay/ApplePay expect the prices to be formatted in cents. @@ -34,10 +35,16 @@ export const transformPrice = ( price, priceObject ) => { * - https://docs.stripe.com/js/elements_object/express_checkout_element_shippingaddresschange_event * - https://docs.stripe.com/js/elements_object/express_checkout_element_shippingratechange_event * - * @param {Object} cartData Store API Cart response object. + * @param {Object} rawCartData Store API Cart response object. * @return {{pending: boolean, name: string, amount: integer}} `displayItems` for Stripe. */ -export const transformCartDataForDisplayItems = ( cartData ) => { +export const transformCartDataForDisplayItems = ( rawCartData ) => { + // allowing extensions to manipulate the individual items returned by the backend. + const cartData = applyFilters( + 'wcpay.express-checkout.map-line-items', + rawCartData + ); + const displayItems = cartData.items.map( ( item ) => ( { amount: transformPrice( parseInt( item.totals?.line_subtotal || item.prices.price, 10 ), From 863519dde8a28374fcf6d231f2d21161bd34edd9 Mon Sep 17 00:00:00 2001 From: Adam Heckler <5512652+aheckler@users.noreply.github.com> Date: Thu, 19 Dec 2024 10:49:37 -0500 Subject: [PATCH 69/83] Use "currency conversion fee" (#10002) Co-authored-by: Nagesh Pai --- changelog/fix-9996-currency-conversion-fee-phrasing | 4 ++++ client/payment-details/timeline/map-events.js | 4 ++-- .../timeline/test/__snapshots__/map-events.js.snap | 2 +- client/utils/account-fees.tsx | 2 +- client/utils/test/__snapshots__/account-fees.tsx.snap | 4 ++-- includes/class-wc-payments-captured-event-note.php | 4 ++-- tests/fixtures/captured-payments/discount.json | 2 +- tests/fixtures/captured-payments/foreign-card.json | 2 +- tests/fixtures/captured-payments/fx-decimal.json | 2 +- tests/fixtures/captured-payments/fx-partial-capture.json | 2 +- tests/fixtures/captured-payments/fx-with-capped-fee.json | 2 +- tests/fixtures/captured-payments/fx.json | 2 +- tests/fixtures/captured-payments/jpy-payment.json | 2 +- tests/fixtures/captured-payments/subscription.json | 2 +- 14 files changed, 20 insertions(+), 16 deletions(-) create mode 100644 changelog/fix-9996-currency-conversion-fee-phrasing diff --git a/changelog/fix-9996-currency-conversion-fee-phrasing b/changelog/fix-9996-currency-conversion-fee-phrasing new file mode 100644 index 00000000000..bdee2cbc00f --- /dev/null +++ b/changelog/fix-9996-currency-conversion-fee-phrasing @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Use "currency conversion fee" instead "foreign exchange fee" in payment timeline and various other places. diff --git a/client/payment-details/timeline/map-events.js b/client/payment-details/timeline/map-events.js index b76f659ac63..7e5570ce8ce 100644 --- a/client/payment-details/timeline/map-events.js +++ b/client/payment-details/timeline/map-events.js @@ -410,12 +410,12 @@ export const feeBreakdown = ( event ) => { fixedRate !== 0 ? __( /* translators: %1$s% is the fee percentage and %2$s is the fixed rate */ - 'Foreign exchange fee: %1$s%% + %2$s', + 'Currency conversion fee: %1$s%% + %2$s', 'woocommerce-payments' ) : __( /* translators: %1$s% is the fee percentage */ - 'Foreign exchange fee: %1$s%%', + 'Currency conversion fee: %1$s%%', 'woocommerce-payments' ), 'additional-wcpay-subscription': diff --git a/client/payment-details/timeline/test/__snapshots__/map-events.js.snap b/client/payment-details/timeline/test/__snapshots__/map-events.js.snap index 4126cb1ff38..b229ba674be 100644 --- a/client/payment-details/timeline/test/__snapshots__/map-events.js.snap +++ b/client/payment-details/timeline/test/__snapshots__/map-events.js.snap @@ -397,7 +397,7 @@ exports[`mapTimelineEvents single currency events formats captured events with f International card fee: 1.5%
  • - Foreign exchange fee: 2% + Currency conversion fee: 2%
  • Discount diff --git a/client/utils/account-fees.tsx b/client/utils/account-fees.tsx index 711d3d337ed..a1c8056f78d 100644 --- a/client/utils/account-fees.tsx +++ b/client/utils/account-fees.tsx @@ -166,7 +166,7 @@ export const formatMethodFeesTooltip = ( ) } { hasFees( accountFees.fx ) ? (
    -
    Foreign exchange fee
    +
    Currency conversion fee
    { getFeeDescriptionString( accountFees.fx ) }
    ) : ( diff --git a/client/utils/test/__snapshots__/account-fees.tsx.snap b/client/utils/test/__snapshots__/account-fees.tsx.snap index 89321bc7582..d92aa6ae54e 100644 --- a/client/utils/test/__snapshots__/account-fees.tsx.snap +++ b/client/utils/test/__snapshots__/account-fees.tsx.snap @@ -23,7 +23,7 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base
  • - Foreign exchange fee + Currency conversion fee
    1% @@ -102,7 +102,7 @@ exports[`Account fees utility functions formatMethodFeesTooltip() displays base
    - Foreign exchange fee + Currency conversion fee
    1% diff --git a/includes/class-wc-payments-captured-event-note.php b/includes/class-wc-payments-captured-event-note.php index 10c48567952..07e902d8632 100644 --- a/includes/class-wc-payments-captured-event-note.php +++ b/includes/class-wc-payments-captured-event-note.php @@ -327,9 +327,9 @@ private function fee_label_mapping( int $fixed_rate, bool $is_capped ) { $res['additional-fx'] = 0 !== $fixed_rate /* translators: %1$s% is the fee percentage and %2$s is the fixed rate */ - ? __( 'Foreign exchange fee: %1$s%% + %2$s', 'woocommerce-payments' ) + ? __( 'Currency conversion fee: %1$s%% + %2$s', 'woocommerce-payments' ) /* translators: %1$s% is the fee percentage */ - : __( 'Foreign exchange fee: %1$s%%', 'woocommerce-payments' ); + : __( 'Currency conversion fee: %1$s%%', 'woocommerce-payments' ); $res['additional-wcpay-subscription'] = 0 !== $fixed_rate /* translators: %1$s% is the fee percentage and %2$s is the fixed rate */ diff --git a/tests/fixtures/captured-payments/discount.json b/tests/fixtures/captured-payments/discount.json index 2fa6a911d74..5bf6f936c45 100644 --- a/tests/fixtures/captured-payments/discount.json +++ b/tests/fixtures/captured-payments/discount.json @@ -60,7 +60,7 @@ "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", "additional-international": "International card fee: 1%", - "additional-fx": "Foreign exchange fee: 1%", + "additional-fx": "Currency conversion fee: 1%", "discount": { "label": "Discount", "variable": "Variable fee: -4.9%", diff --git a/tests/fixtures/captured-payments/foreign-card.json b/tests/fixtures/captured-payments/foreign-card.json index 234878b2372..df45c326d62 100644 --- a/tests/fixtures/captured-payments/foreign-card.json +++ b/tests/fixtures/captured-payments/foreign-card.json @@ -53,7 +53,7 @@ "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", "additional-international": "International card fee: 1%", - "additional-fx": "Foreign exchange fee: 1%" + "additional-fx": "Currency conversion fee: 1%" }, "netString": "Net payout: $95.47 USD" } diff --git a/tests/fixtures/captured-payments/fx-decimal.json b/tests/fixtures/captured-payments/fx-decimal.json index b95e9318c84..2f065036122 100644 --- a/tests/fixtures/captured-payments/fx-decimal.json +++ b/tests/fixtures/captured-payments/fx-decimal.json @@ -45,7 +45,7 @@ "feeString": "Fee (3.9% + $0.30): -$4.39", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", - "additional-fx": "Foreign exchange fee: 1%" + "additional-fx": "Currency conversion fee: 1%" }, "netString": "Net payout: $100.65 USD" } diff --git a/tests/fixtures/captured-payments/fx-partial-capture.json b/tests/fixtures/captured-payments/fx-partial-capture.json index f10ff7aa9e9..691390d4852 100644 --- a/tests/fixtures/captured-payments/fx-partial-capture.json +++ b/tests/fixtures/captured-payments/fx-partial-capture.json @@ -57,7 +57,7 @@ "feeString": "Fee (3.51% + £0.21): -$0.88", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", - "additional-fx": "Foreign exchange fee: 1%", + "additional-fx": "Currency conversion fee: 1%", "discount": { "label": "Discount", "variable": "Variable fee: -0.39%", diff --git a/tests/fixtures/captured-payments/fx-with-capped-fee.json b/tests/fixtures/captured-payments/fx-with-capped-fee.json index 8c1b602a3eb..4c31a8435d7 100644 --- a/tests/fixtures/captured-payments/fx-with-capped-fee.json +++ b/tests/fixtures/captured-payments/fx-with-capped-fee.json @@ -55,7 +55,7 @@ "feeBreakdown": { "base": "Base fee: capped at $6.00", "additional-international": "International card fee: 1.5%", - "additional-fx": "Foreign exchange fee: 1%" + "additional-fx": "Currency conversion fee: 1%" }, "netString": "Net payout: $971.04 USD" } diff --git a/tests/fixtures/captured-payments/fx.json b/tests/fixtures/captured-payments/fx.json index 8ceee7b7438..f18ca9297ab 100644 --- a/tests/fixtures/captured-payments/fx.json +++ b/tests/fixtures/captured-payments/fx.json @@ -46,7 +46,7 @@ "feeString": "Fee (3.9% + $0.30): -$4.20", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", - "additional-fx": "Foreign exchange fee: 1%" + "additional-fx": "Currency conversion fee: 1%" }, "netString": "Net payout: $95.84 USD" } diff --git a/tests/fixtures/captured-payments/jpy-payment.json b/tests/fixtures/captured-payments/jpy-payment.json index 6c7a6b3ee05..4b4c6c152c9 100644 --- a/tests/fixtures/captured-payments/jpy-payment.json +++ b/tests/fixtures/captured-payments/jpy-payment.json @@ -57,7 +57,7 @@ "feeBreakdown": { "base": "Base fee: 3.6%", "additional-international": "International card fee: 2%", - "additional-fx": "Foreign exchange fee: 2%" + "additional-fx": "Currency conversion fee: 2%" }, "netString": "Net payout: ¥4,507 JPY" } diff --git a/tests/fixtures/captured-payments/subscription.json b/tests/fixtures/captured-payments/subscription.json index b7312ea0c02..d0e1fe705e4 100644 --- a/tests/fixtures/captured-payments/subscription.json +++ b/tests/fixtures/captured-payments/subscription.json @@ -53,7 +53,7 @@ "feeString": "Fee (4.9% + $0.30): -$3.04", "feeBreakdown": { "base": "Base fee: 2.9% + $0.30", - "additional-fx": "Foreign exchange fee: 1%", + "additional-fx": "Currency conversion fee: 1%", "additional-wcpay-subscription": "Subscription transaction fee: 1%" }, "netString": "Net payout: $52.87 USD" From 2176d63f1329cd058a49dd8c63315f617b386547 Mon Sep 17 00:00:00 2001 From: Timur Karimov Date: Thu, 19 Dec 2024 17:46:54 +0100 Subject: [PATCH 70/83] Show ECE buttons on products w/o shipping tax included in price, and improve tests (#9954) Co-authored-by: Timur Karimov Co-authored-by: Mike Moore --- .../fix-ece-button-for-price-including-tax | 4 + ...ayments-express-checkout-button-helper.php | 8 +- ...yments-express-checkout-button-handler.php | 136 ++++++++++++++++++ ...ayments-express-checkout-button-helper.php | 112 ++++++--------- 4 files changed, 185 insertions(+), 75 deletions(-) create mode 100644 changelog/fix-ece-button-for-price-including-tax create mode 100644 tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-handler.php diff --git a/changelog/fix-ece-button-for-price-including-tax b/changelog/fix-ece-button-for-price-including-tax new file mode 100644 index 00000000000..521ceb2af68 --- /dev/null +++ b/changelog/fix-ece-button-for-price-including-tax @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Show express checkout for products w/o shipping but where tax is included into price. 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 672f2584c67..86d1a82c54d 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 @@ -415,7 +415,7 @@ public function should_show_express_checkout_button() { return true; } - // Non-shipping product and billing is calculated based on shopper billing addres. Excludes Pay for Order page. + // Non-shipping product and tax is calculated based on shopper billing address. Excludes Pay for Order page. if ( // If the product doesn't needs shipping. ( @@ -426,8 +426,10 @@ public function should_show_express_checkout_button() { ( ( $this->is_cart() || $this->is_checkout() ) && ! WC()->cart->needs_shipping() ) ) - // ...and billing is calculated based on billing address. - && wc_tax_enabled() && 'billing' === get_option( 'woocommerce_tax_based_on' ) + // ...and tax is calculated based on billing address. + && wc_tax_enabled() + && 'billing' === get_option( 'woocommerce_tax_based_on' ) + && 'yes' !== get_option( 'woocommerce_prices_include_tax' ) ) { return false; } diff --git a/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-handler.php b/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-handler.php new file mode 100644 index 00000000000..0b10752c0f5 --- /dev/null +++ b/tests/unit/express-checkout/test-class-wc-payments-express-checkout-button-handler.php @@ -0,0 +1,136 @@ +shipping()->unregister_shipping_methods(); + + $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); + $this->mock_wcpay_gateway = $this->createMock( WC_Payment_Gateway_WCPay::class ); + $this->mock_ece_button_helper = $this->createMock( WC_Payments_Express_Checkout_Button_Helper::class ); + $this->mock_express_checkout_ajax_handler = $this->createMock( WC_Payments_Express_Checkout_Ajax_Handler::class ); + + $this->system_under_test = new WC_Payments_Express_Checkout_Button_Handler( + $this->mock_wcpay_account, + $this->mock_wcpay_gateway, + $this->mock_ece_button_helper, + $this->mock_express_checkout_ajax_handler + ); + + // Set up shipping zones and methods. + $this->zone = new WC_Shipping_Zone(); + $this->zone->set_zone_name( 'Worldwide' ); + $this->zone->set_zone_order( 1 ); + $this->zone->save(); + + $flat_rate = $this->zone->add_shipping_method( 'flat_rate' ); + $this->flat_rate_id = $flat_rate; + + $local_pickup = $this->zone->add_shipping_method( 'local_pickup' ); + $this->local_pickup_id = $local_pickup; + } + + public function tear_down() { + parent::tear_down(); + + // Clean up shipping zones and methods. + $this->zone->delete(); + } + + public function test_filter_cart_needs_shipping_address_regular_products() { + $this->assertEquals( + true, + $this->system_under_test->filter_cart_needs_shipping_address( true ), + 'Should not modify shipping address requirement for regular products' + ); + } + + + public function test_filter_cart_needs_shipping_address_subscription_products() { + WC_Subscriptions_Cart::set_cart_contains_subscription( true ); + $this->mock_ece_button_helper->method( 'is_checkout' )->willReturn( true ); + + $this->zone->delete_shipping_method( $this->flat_rate_id ); + $this->zone->delete_shipping_method( $this->local_pickup_id ); + + $this->assertFalse( + $this->system_under_test->filter_cart_needs_shipping_address( true ), + 'Should not require shipping address for subscription without shipping methods' + ); + + remove_filter( 'woocommerce_shipping_method_count', '__return_zero' ); + WC_Subscriptions_Cart::set_cart_contains_subscription( false ); + } +} 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 2432c61172c..8006faac78f 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 @@ -28,13 +28,6 @@ class WC_Payments_Express_Checkout_Button_Helper_Test extends WCPAY_UnitTestCase */ private $mock_wcpay_account; - /** - * Express Checkout Helper instance. - * - * @var WC_Payments_Express_Checkout_Button_Helper - */ - private $express_checkout_helper; - /** * Test shipping zone. * @@ -61,21 +54,7 @@ class WC_Payments_Express_Checkout_Button_Helper_Test extends WCPAY_UnitTestCase * * @var WC_Payments_Express_Checkout_Button_Helper */ - private $mock_express_checkout_helper; - - /** - * Express Checkout Ajax Handler instance. - * - * @var WC_Payments_Express_Checkout_Ajax_Handler - */ - private $mock_express_checkout_ajax_handler; - - /** - * Express Checkout ECE Button Handler instance. - * - * @var WC_Payments_Express_Checkout_Button_Handler - */ - private $mock_express_checkout_ece_button_handler; + private $system_under_test; /** * Test product to add to the cart @@ -92,23 +71,7 @@ public function set_up() { $this->mock_wcpay_account = $this->createMock( WC_Payments_Account::class ); $this->mock_wcpay_gateway = $this->make_wcpay_gateway(); - $this->mock_express_checkout_helper = new WC_Payments_Express_Checkout_Button_Helper( $this->mock_wcpay_gateway, $this->mock_wcpay_account ); - $this->mock_express_checkout_ajax_handler = $this->getMockBuilder( WC_Payments_Express_Checkout_Ajax_Handler::class ) - ->setConstructorArgs( - [ - $this->mock_express_checkout_helper, - ] - ) - ->getMock(); - - $this->mock_ece_button_helper = $this->getMockBuilder( WC_Payments_Express_Checkout_Button_Helper::class ) - ->setConstructorArgs( - [ - $this->mock_wcpay_gateway, - $this->mock_wcpay_account, - ] - ) - ->getMock(); + $this->system_under_test = new WC_Payments_Express_Checkout_Button_Helper( $this->mock_wcpay_gateway, $this->mock_wcpay_account ); WC_Helper_Shipping::delete_simple_flat_rate(); $zone = new WC_Shipping_Zone(); @@ -128,7 +91,7 @@ public function set_up() { WC()->session->init(); WC()->cart->add_to_cart( $this->simple_product->get_id(), 1 ); - $this->mock_express_checkout_helper->update_shipping_method( [ self::get_shipping_option_rate_id( $this->flat_rate_id ) ] ); + $this->system_under_test->update_shipping_method( [ self::get_shipping_option_rate_id( $this->flat_rate_id ) ] ); WC()->cart->calculate_totals(); } @@ -195,34 +158,34 @@ public function test_common_get_button_settings() { 'height' => '48', 'radius' => '', ], - $this->mock_express_checkout_helper->get_common_button_settings() + $this->system_under_test->get_common_button_settings() ); } public function test_cart_prices_include_tax_with_tax_disabled() { add_filter( 'wc_tax_enabled', '__return_false' ); - $this->assertTrue( $this->mock_express_checkout_helper->cart_prices_include_tax() ); + $this->assertTrue( $this->system_under_test->cart_prices_include_tax() ); } public function test_cart_prices_include_tax_with_tax_enabled_and_display_incl() { add_filter( 'wc_tax_enabled', '__return_true' ); // reset in tear_down. add_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_incl' ] ); // reset in tear_down. - $this->assertTrue( $this->mock_express_checkout_helper->cart_prices_include_tax() ); + $this->assertTrue( $this->system_under_test->cart_prices_include_tax() ); } public function test_cart_prices_include_tax_with_tax_enabled_and_display_excl() { add_filter( 'wc_tax_enabled', '__return_true' ); // reset in tear_down. add_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_excl' ] ); // reset in tear_down. - $this->assertFalse( $this->mock_express_checkout_helper->cart_prices_include_tax() ); + $this->assertFalse( $this->system_under_test->cart_prices_include_tax() ); } public function test_get_total_label() { $this->mock_wcpay_account->method( 'get_statement_descriptor' ) ->willReturn( 'Google Pay' ); - $result = $this->mock_express_checkout_helper->get_total_label(); + $result = $this->system_under_test->get_total_label(); $this->assertEquals( 'Google Pay (via WooCommerce)', $result ); } @@ -238,49 +201,54 @@ function () { } ); - $result = $this->mock_express_checkout_helper->get_total_label(); + $result = $this->system_under_test->get_total_label(); $this->assertEquals( 'Google Pay (via WooPayments)', $result ); remove_all_filters( 'wcpay_payment_request_total_label_suffix' ); } - public function test_filter_cart_needs_shipping_address_returns_false() { - sleep( 1 ); - $this->zone->delete_shipping_method( $this->flat_rate_id ); - $this->zone->delete_shipping_method( $this->local_pickup_id ); + public function test_should_show_express_checkout_button_for_non_shipping_but_price_includes_tax() { + $this->mock_wcpay_account + ->method( 'is_stripe_connected' ) + ->willReturn( true ); - WC_Subscriptions_Cart::set_cart_contains_subscription( true ); + WC_Payments::mode()->dev(); - $this->mock_ece_button_helper - ->method( 'is_product' ) - ->willReturn( true ); + add_filter( 'woocommerce_is_checkout', '__return_true' ); + add_filter( 'wc_shipping_enabled', '__return_false' ); + add_filter( 'wc_tax_enabled', '__return_true' ); - $this->mock_express_checkout_ece_button_handler = new WC_Payments_Express_Checkout_Button_Handler( - $this->mock_wcpay_account, - $this->mock_wcpay_gateway, - $this->mock_ece_button_helper, - $this->mock_express_checkout_ajax_handler - ); + update_option( 'woocommerce_tax_based_on', 'billing' ); + update_option( 'woocommerce_prices_include_tax', 'yes' ); - $this->assertFalse( $this->mock_express_checkout_ece_button_handler->filter_cart_needs_shipping_address( true ) ); + $this->assertTrue( $this->system_under_test->should_show_express_checkout_button() ); + + remove_filter( 'woocommerce_is_checkout', '__return_true' ); + remove_filter( 'wc_tax_enabled', '__return_true' ); + remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_incl' ] ); } - public function test_filter_cart_needs_shipping_address_returns_true() { - WC_Subscriptions_Cart::set_cart_contains_subscription( true ); - $this->mock_ece_button_helper - ->method( 'is_product' ) + 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' ) ->willReturn( true ); - $this->mock_express_checkout_ece_button_handler = new WC_Payments_Express_Checkout_Button_Handler( - $this->mock_wcpay_account, - $this->mock_wcpay_gateway, - $this->mock_ece_button_helper, - $this->mock_express_checkout_ajax_handler - ); + WC_Payments::mode()->dev(); + + add_filter( 'woocommerce_is_checkout', '__return_true' ); + add_filter( 'wc_shipping_enabled', '__return_false' ); + add_filter( 'wc_tax_enabled', '__return_true' ); + + update_option( 'woocommerce_tax_based_on', 'billing' ); + update_option( 'woocommerce_prices_include_tax', 'no' ); - $this->assertTrue( $this->mock_express_checkout_ece_button_handler->filter_cart_needs_shipping_address( true ) ); + $this->assertFalse( $this->system_under_test->should_show_express_checkout_button() ); + + remove_filter( 'woocommerce_is_checkout', '__return_true' ); + remove_filter( 'wc_tax_enabled', '__return_true' ); + remove_filter( 'pre_option_woocommerce_tax_display_cart', [ $this, '__return_incl' ] ); } /** From 732ab9286b19d0210933cd80c1909e8c5f33f912 Mon Sep 17 00:00:00 2001 From: Alefe Souza Date: Thu, 19 Dec 2024 14:17:26 -0300 Subject: [PATCH 71/83] Add WooPay Klaviyo newsletter integration (#9908) --- changelog/add-woopay-klaviyo-newsletter-support | 4 ++++ includes/compat/blocks/class-blocks-data-extractor.php | 9 +++++++++ 2 files changed, 13 insertions(+) create mode 100644 changelog/add-woopay-klaviyo-newsletter-support diff --git a/changelog/add-woopay-klaviyo-newsletter-support b/changelog/add-woopay-klaviyo-newsletter-support new file mode 100644 index 00000000000..64e94c6638e --- /dev/null +++ b/changelog/add-woopay-klaviyo-newsletter-support @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Add WooPay Klaviyo newsletter integration. diff --git a/includes/compat/blocks/class-blocks-data-extractor.php b/includes/compat/blocks/class-blocks-data-extractor.php index 673cae7f352..becc393a5da 100644 --- a/includes/compat/blocks/class-blocks-data-extractor.php +++ b/includes/compat/blocks/class-blocks-data-extractor.php @@ -59,6 +59,15 @@ private function get_available_blocks() { $blocks[] = new \Mailchimp_Woocommerce_Newsletter_Blocks_Integration(); } + if ( class_exists( '\WCK\Blocks\CheckoutIntegration' ) ) { + // phpcs:ignore + /** + * @psalm-suppress UndefinedClass + * @phpstan-ignore-next-line + */ + $blocks[] = new \WCK\Blocks\CheckoutIntegration(); + } + return $blocks; } From 915fcc89ac17370b91a825a46b203eff54e73369 Mon Sep 17 00:00:00 2001 From: Ricardo Metring Date: Thu, 19 Dec 2024 20:11:44 -0300 Subject: [PATCH 72/83] Fix FedEx shipping multi-currency rates (#9985) Co-authored-by: Rafael Zaleski --- changelog/fix-198-mccy-fedex-conversion | 5 ++ .../Compatibility/WooCommerceFedEx.php | 45 ++++++++++---- .../test-class-woocommerce-fedex.php | 60 +++++++++++++------ 3 files changed, 81 insertions(+), 29 deletions(-) create mode 100644 changelog/fix-198-mccy-fedex-conversion diff --git a/changelog/fix-198-mccy-fedex-conversion b/changelog/fix-198-mccy-fedex-conversion new file mode 100644 index 00000000000..7fecbc49b87 --- /dev/null +++ b/changelog/fix-198-mccy-fedex-conversion @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Fix FedEx insurance rates with different currencies. + + diff --git a/includes/multi-currency/Compatibility/WooCommerceFedEx.php b/includes/multi-currency/Compatibility/WooCommerceFedEx.php index 8a38d058e40..15c25b4ba27 100644 --- a/includes/multi-currency/Compatibility/WooCommerceFedEx.php +++ b/includes/multi-currency/Compatibility/WooCommerceFedEx.php @@ -8,13 +8,25 @@ namespace WCPay\MultiCurrency\Compatibility; use WCPay\MultiCurrency\MultiCurrency; -use WCPay\MultiCurrency\Utils; /** * Class that controls Multi Currency Compatibility with WooCommerce FedEx Plugin. */ class WooCommerceFedEx extends BaseCompatibility { + /** + * Calls to look for in the backtrace when determining whether + * to return store currency or skip converting product prices. + */ + private const WC_SHIPPING_FEDEX_CALLS = [ + 'WC_Shipping_Fedex->set_settings', + 'WC_Shipping_Fedex->per_item_shipping', + 'WC_Shipping_Fedex->box_shipping', + 'WC_Shipping_Fedex->get_fedex_api_request', + 'WC_Shipping_Fedex->get_fedex_requests', + 'WC_Shipping_Fedex->process_result', + ]; + /** * Init the class. * @@ -23,10 +35,31 @@ class WooCommerceFedEx extends BaseCompatibility { public function init() { // Add needed actions and filters if FedEx is active. if ( class_exists( 'WC_Shipping_Fedex_Init' ) ) { + add_filter( MultiCurrency::FILTER_PREFIX . 'should_convert_product_price', [ $this, 'should_convert_product_price' ] ); add_filter( MultiCurrency::FILTER_PREFIX . 'should_return_store_currency', [ $this, 'should_return_store_currency' ] ); } } + /** + * Checks to see if the product's price should be converted. + * + * @param bool $return Whether to convert the product's price or not. Default is true. + * + * @return bool True if it should be converted. + */ + public function should_convert_product_price( bool $return ): bool { + // If it's already false, return it. + if ( ! $return ) { + return $return; + } + + if ( $this->utils->is_call_in_backtrace( self::WC_SHIPPING_FEDEX_CALLS ) ) { + return false; + } + + return $return; + } + /** * Determine whether to return the store currency or not. * @@ -40,15 +73,7 @@ public function should_return_store_currency( bool $return ): bool { return $return; } - $calls = [ - 'WC_Shipping_Fedex->set_settings', - 'WC_Shipping_Fedex->per_item_shipping', - 'WC_Shipping_Fedex->box_shipping', - 'WC_Shipping_Fedex->get_fedex_api_request', - 'WC_Shipping_Fedex->get_fedex_requests', - 'WC_Shipping_Fedex->process_result', - ]; - if ( $this->utils->is_call_in_backtrace( $calls ) ) { + if ( $this->utils->is_call_in_backtrace( self::WC_SHIPPING_FEDEX_CALLS ) ) { return true; } diff --git a/tests/unit/multi-currency/compatibility/test-class-woocommerce-fedex.php b/tests/unit/multi-currency/compatibility/test-class-woocommerce-fedex.php index 60e130390fd..e52927230ca 100644 --- a/tests/unit/multi-currency/compatibility/test-class-woocommerce-fedex.php +++ b/tests/unit/multi-currency/compatibility/test-class-woocommerce-fedex.php @@ -35,6 +35,20 @@ class WCPay_Multi_Currency_WooCommerceFedEx_Tests extends WCPAY_UnitTestCase { */ private $woocommerce_fedex; + /** + * Calls to check in the backtrace. + * + * @var array + */ + private $woocommerce_fedex_calls = [ + 'WC_Shipping_Fedex->set_settings', + 'WC_Shipping_Fedex->per_item_shipping', + 'WC_Shipping_Fedex->box_shipping', + 'WC_Shipping_Fedex->get_fedex_api_request', + 'WC_Shipping_Fedex->get_fedex_requests', + 'WC_Shipping_Fedex->process_result', + ]; + /** * Pre-test setup */ @@ -54,37 +68,45 @@ public function test_should_return_store_currency_returns_true_if_true_passed() // If the calls are found, it should return true. public function test_should_return_store_currency_returns_true_if_calls_found() { - $calls = [ - 'WC_Shipping_Fedex->set_settings', - 'WC_Shipping_Fedex->per_item_shipping', - 'WC_Shipping_Fedex->box_shipping', - 'WC_Shipping_Fedex->get_fedex_api_request', - 'WC_Shipping_Fedex->get_fedex_requests', - 'WC_Shipping_Fedex->process_result', - ]; $this->mock_utils ->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) - ->with( $calls ) + ->with( $this->woocommerce_fedex_calls ) ->willReturn( true ); + $this->assertTrue( $this->woocommerce_fedex->should_return_store_currency( false ) ); } - // If the calls are found, it should return true. + // If the calls are not found, it should return false. public function test_should_return_store_currency_returns_false_if_no_calls_found() { - $calls = [ - 'WC_Shipping_Fedex->set_settings', - 'WC_Shipping_Fedex->per_item_shipping', - 'WC_Shipping_Fedex->box_shipping', - 'WC_Shipping_Fedex->get_fedex_api_request', - 'WC_Shipping_Fedex->get_fedex_requests', - 'WC_Shipping_Fedex->process_result', - ]; $this->mock_utils ->expects( $this->once() ) ->method( 'is_call_in_backtrace' ) - ->with( $calls ) + ->with( $this->woocommerce_fedex_calls ) ->willReturn( false ); + $this->assertFalse( $this->woocommerce_fedex->should_return_store_currency( false ) ); } + + // If true is passed to should_convert_product_price and no calls are found, it should return true. + public function test_should_convert_product_price_returns_true_if_true_passed_and_no_calls_found() { + $this->mock_utils + ->expects( $this->once() ) + ->method( 'is_call_in_backtrace' ) + ->with( $this->woocommerce_fedex_calls ) + ->willReturn( false ); + + $this->assertTrue( $this->woocommerce_fedex->should_convert_product_price( true ) ); + } + + // If calls are found, should_convert_product_price should return false even if true was passed. + public function test_should_convert_product_price_returns_false_if_calls_found() { + $this->mock_utils + ->expects( $this->once() ) + ->method( 'is_call_in_backtrace' ) + ->with( $this->woocommerce_fedex_calls ) + ->willReturn( true ); + + $this->assertFalse( $this->woocommerce_fedex->should_convert_product_price( true ) ); + } } From 779ef47509e3907aeab9e2a0a7f693e1a9d3d5d7 Mon Sep 17 00:00:00 2001 From: Miguel Gasca Date: Fri, 20 Dec 2024 10:21:49 +0100 Subject: [PATCH 73/83] Translate hard coded strings in the fees tooltip breakdown (#10004) --- ...rakedown-tooltip-are-not-internationalised | 4 ++++ client/utils/account-fees.tsx | 20 +++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised diff --git a/changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised b/changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised new file mode 100644 index 00000000000..57528510958 --- /dev/null +++ b/changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Use translatable strings on the fee breakdown tooltip of the payment settings screen. diff --git a/client/utils/account-fees.tsx b/client/utils/account-fees.tsx index a1c8056f78d..791b727b164 100644 --- a/client/utils/account-fees.tsx +++ b/client/utils/account-fees.tsx @@ -143,7 +143,7 @@ export const formatMethodFeesTooltip = ( return (
    -
    Base fee
    +
    { __( 'Base fee', 'woocommerce-payments' ) }
    { getFeeDescriptionString( accountFees.base, @@ -153,7 +153,12 @@ export const formatMethodFeesTooltip = (
    { hasFees( accountFees.additional ) ? (
    -
    International payment method fee
    +
    + { __( + 'International payment method fee', + 'woocommerce-payments' + ) } +
    { getFeeDescriptionString( accountFees.additional, @@ -166,14 +171,21 @@ export const formatMethodFeesTooltip = ( ) } { hasFees( accountFees.fx ) ? (
    -
    Currency conversion fee
    +
    + { __( + 'Currency conversion fee', + 'woocommerce-payments' + ) } +
    { getFeeDescriptionString( accountFees.fx ) }
    ) : ( '' ) }
    -
    Total per transaction
    +
    + { __( 'Total per transaction', 'woocommerce-payments' ) } +
    { getFeeDescriptionString( total ) }
    From 3c7b1155c4e3db7e8dbe5cb70cccf19a1fc40ae9 Mon Sep 17 00:00:00 2001 From: Zvonimir Maglica Date: Fri, 20 Dec 2024 11:00:47 +0100 Subject: [PATCH 74/83] Invalidate transactions store when transaction is captured (#10015) --- .../fix-9735-render-transactions-correctly-on-capture | 4 ++++ client/data/authorizations/actions.ts | 7 +++++++ client/data/authorizations/test/actions.test.ts | 8 ++++++++ 3 files changed, 19 insertions(+) create mode 100644 changelog/fix-9735-render-transactions-correctly-on-capture diff --git a/changelog/fix-9735-render-transactions-correctly-on-capture b/changelog/fix-9735-render-transactions-correctly-on-capture new file mode 100644 index 00000000000..9ed7f628e4c --- /dev/null +++ b/changelog/fix-9735-render-transactions-correctly-on-capture @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Ensure captured transactions appear in the Transactions tab without requiring a page refresh. diff --git a/client/data/authorizations/actions.ts b/client/data/authorizations/actions.ts index 0885da1cbfe..ace7f3c6fed 100644 --- a/client/data/authorizations/actions.ts +++ b/client/data/authorizations/actions.ts @@ -196,6 +196,13 @@ export function* submitCaptureAuthorization( 'getPaymentIntent' ); + // Need to invalidate transactions tab to update newly captured transaction if needed. + yield controls.dispatch( + STORE_NAME, + 'invalidateResolutionForStoreSelector', + 'getTransactions' + ); + // Create success notice. yield controls.dispatch( 'core/notices', diff --git a/client/data/authorizations/test/actions.test.ts b/client/data/authorizations/test/actions.test.ts index 171ef6dd5ad..36527d1836a 100644 --- a/client/data/authorizations/test/actions.test.ts +++ b/client/data/authorizations/test/actions.test.ts @@ -118,6 +118,14 @@ describe( 'Authorizations actions', () => { ) ); + expect( generator.next().value ).toEqual( + controls.dispatch( + 'wc/payments', + 'invalidateResolutionForStoreSelector', + 'getTransactions' + ) + ); + expect( generator.next().value ).toEqual( controls.dispatch( 'core/notices', From f0ebc14465fa5f66268b22b358ab6dcde4085a58 Mon Sep 17 00:00:00 2001 From: Vlad Olaru Date: Fri, 20 Dec 2024 12:48:43 +0200 Subject: [PATCH 75/83] Reduce API impact from test drive setup (#10014) Co-authored-by: Dan Paun <82826872+dpaun1985@users.noreply.github.com> --- ...837-reduce-api-impact-for-test-drive-setup | 5 +++ client/connect-account-page/index.tsx | 32 +++++++++++++++---- 2 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 changelog/update-s6837-reduce-api-impact-for-test-drive-setup diff --git a/changelog/update-s6837-reduce-api-impact-for-test-drive-setup b/changelog/update-s6837-reduce-api-impact-for-test-drive-setup new file mode 100644 index 00000000000..eaa0360fe7a --- /dev/null +++ b/changelog/update-s6837-reduce-api-impact-for-test-drive-setup @@ -0,0 +1,5 @@ +Significance: patch +Type: update +Comment: Reduce the maximum number of API calls during the test-drive process and add maximum duration cut off logic. + + diff --git a/client/connect-account-page/index.tsx b/client/connect-account-page/index.tsx index 2b3f402abcb..42cb5f25f88 100644 --- a/client/connect-account-page/index.tsx +++ b/client/connect-account-page/index.tsx @@ -95,6 +95,16 @@ const ConnectAccountPage: React.FC = () => { const loaderProgressRef = useRef( testDriveLoaderProgress ); loaderProgressRef.current = testDriveLoaderProgress; + // Use a timer to track the elapsed time for the test drive mode setup. + let testDriveSetupStartTime: number; + // The test drive setup will be forced finished after 40 seconds + // (10 seconds for the initial calls plus 30 for checking the account status in a loop). + const testDriveSetupMaxDuration = 40; + + // Helper function to calculate the elapsed time in seconds. + const elapsed = ( time: number ) => + Math.round( ( Date.now() - time ) / 1000 ); + const { connectUrl, connect: { availableCountries, country }, @@ -173,19 +183,20 @@ const ConnectAccountPage: React.FC = () => { method: 'GET', } ).then( ( account ) => { // Simulate the update of the loader progress bar by 4% per check. - // Limit to a maximum of 15 checks or 30 seconds. - updateLoaderProgress( 100, 4 ); + // Limit to a maximum of 10 checks (6% progress per each request starting from 40% = max 10 checks). + updateLoaderProgress( 100, 6 ); - // If the account status is not a pending one or progress percentage is above 95, - // consider our work done and redirect the merchant. - // Otherwise, schedule another check after 2 seconds. + // If the account status is not a pending one, the progress percentage is above 95, + // or we've exceeded the timeout, consider our work done and redirect the merchant. + // Otherwise, schedule another check after a 2.5 seconds wait. if ( ( account && ( account as AccountData ).status && ! ( account as AccountData ).status.includes( 'pending' ) ) || - loaderProgressRef.current > 95 + loaderProgressRef.current > 95 || + elapsed( testDriveSetupStartTime ) > testDriveSetupMaxDuration ) { setTestDriveLoaderProgress( 100 ); const queryArgs = { @@ -203,12 +214,18 @@ const ConnectAccountPage: React.FC = () => { ...extraQueryArgs, } ); } else { - setTimeout( () => checkAccountStatus( extraQueryArgs ), 2000 ); + // Schedule another check after 2.5 seconds. + // 2.5 seconds plus 0.5 seconds for the fetch request is 3 seconds. + // With a maximum of 10 checks, we will wait for 30 seconds before ending the process normally. + setTimeout( () => checkAccountStatus( extraQueryArgs ), 2500 ); } } ); }; const handleSetupTestDriveMode = async () => { + // Record the start time of the test drive setup. + testDriveSetupStartTime = Date.now(); + // Initialize the progress bar. setTestDriveLoaderProgress( 5 ); setTestDriveModeSubmitted( true ); trackConnectAccountClicked( true ); @@ -256,6 +273,7 @@ const ConnectAccountPage: React.FC = () => { } clearInterval( updateProgress ); + // Update the progress bar to 40% since we've finished the initial account setup. setTestDriveLoaderProgress( 40 ); // Check the url for the `wcpay-connection-success` parameter, indicating a successful connection. From 3794508c29cdc11e37eae148ee12d58252311782 Mon Sep 17 00:00:00 2001 From: Miguel Gasca Date: Fri, 20 Dec 2024 12:14:15 +0100 Subject: [PATCH 76/83] Apply User-Defined Date Formatting Settings to WP Admin React Components (#9635) Co-authored-by: deepakpathania <68396823+deepakpathania@users.noreply.github.com> --- ...atting-arent-respected-in-react-components | 4 + client/capital/index.tsx | 12 +- .../test/__snapshots__/index.test.tsx.snap | 48 +++++ client/capital/test/index.test.tsx | 2 + .../account-fees/expiration-description.js | 7 +- .../account-status/account-fees/test/index.js | 1 + .../components/active-loan-summary/index.tsx | 20 +- .../test/__snapshots__/index.js.snap | 2 +- .../active-loan-summary/test/index.js | 10 + .../components/date-format-notice/index.tsx | 67 +++++++ .../components/date-format-notice/style.scss | 5 + .../deposits-overview/test/index.tsx | 2 + .../components/disputed-order-notice/index.js | 12 +- .../disputed-order-notice/test/index.test.js | 1 + client/deposits/details/index.tsx | 11 +- client/deposits/details/test/index.tsx | 2 + client/deposits/index.tsx | 2 + client/deposits/list/index.tsx | 15 +- .../list/test/__snapshots__/index.tsx.snap | 12 +- client/deposits/list/test/index.tsx | 6 +- client/deposits/utils/index.ts | 12 +- client/deposits/utils/test/index.ts | 22 +++ client/disputes/evidence/test/index.js | 3 + client/disputes/index.tsx | 32 ++-- client/disputes/info/index.tsx | 16 +- client/disputes/info/test/index.tsx | 4 + .../test/__snapshots__/index.tsx.snap | 48 +++++ client/disputes/test/index.tsx | 13 +- client/documents/index.tsx | 3 +- client/documents/list/index.tsx | 20 +- client/documents/list/test/index.tsx | 9 + client/globals.d.ts | 3 + client/overview/index.js | 2 + .../modal/update-business-details/index.tsx | 13 +- .../overview/task-list/tasks/dispute-task.tsx | 16 +- .../tasks/update-business-details-task.tsx | 10 +- client/overview/task-list/test/tasks.js | 1 + .../dispute-details/dispute-due-by-date.tsx | 12 +- .../dispute-resolution-footer.tsx | 50 ++--- .../dispute-details/dispute-steps.tsx | 27 +-- .../dispute-details/dispute-summary-row.tsx | 11 +- .../test/__snapshots__/index.test.tsx.snap | 48 +++++ .../order-details/test/index.test.tsx | 4 + .../payment-details/payment-details/index.tsx | 3 +- client/payment-details/readers/index.js | 3 +- client/payment-details/summary/index.tsx | 22 ++- .../test/__snapshots__/index.test.tsx.snap | 4 +- .../summary/test/index.test.tsx | 17 +- .../test/__snapshots__/index.test.tsx.snap | 96 ++++++++++ client/payment-details/test/index.test.tsx | 4 + client/payment-details/timeline/map-events.js | 13 +- client/payment-details/timeline/test/index.js | 1 + .../timeline/test/map-events.js | 1 + client/transactions/blocked/columns.tsx | 10 +- .../blocked/test/columns.test.tsx | 4 + client/transactions/index.tsx | 2 + client/transactions/list/deposit.tsx | 10 +- client/transactions/list/index.tsx | 10 +- .../list/test/__snapshots__/index.tsx.snap | 32 ++-- client/transactions/list/test/deposit.tsx | 20 ++ client/transactions/list/test/index.tsx | 11 ++ client/transactions/risk-review/columns.tsx | 10 +- .../risk-review/test/columns.test.tsx | 4 + client/transactions/uncaptured/index.tsx | 36 ++-- .../uncaptured/test/index.test.tsx | 4 + client/utils/date-time.ts | 82 ++++++++ client/utils/test/date-time.test.ts | 181 ++++++++++++++++++ includes/admin/class-wc-payments-admin.php | 3 + includes/class-wc-payments.php | 1 + 69 files changed, 915 insertions(+), 289 deletions(-) create mode 100644 changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components create mode 100644 client/components/date-format-notice/index.tsx create mode 100644 client/components/date-format-notice/style.scss create mode 100644 client/utils/date-time.ts create mode 100644 client/utils/test/date-time.test.ts diff --git a/changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components b/changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components new file mode 100644 index 00000000000..5c69920cf26 --- /dev/null +++ b/changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Apply User-Defined Date Formatting Settings to WP Admin React Components diff --git a/client/capital/index.tsx b/client/capital/index.tsx index 81e76ad91b4..469b2d283a8 100644 --- a/client/capital/index.tsx +++ b/client/capital/index.tsx @@ -6,7 +6,6 @@ import * as React from 'react'; import { __, _n } from '@wordpress/i18n'; import { TableCard } from '@woocommerce/components'; -import { dateI18n } from '@wordpress/date'; /** * Internal dependencies. @@ -25,6 +24,8 @@ import Chip from 'components/chip'; import { useLoans } from 'wcpay/data'; import { getAdminUrl } from 'wcpay/utils'; import './style.scss'; +import { formatDateTimeFromString } from 'wcpay/utils/date-time'; +import DateFormatNotice from 'wcpay/components/date-format-notice'; const columns = [ { @@ -80,7 +81,7 @@ const getLoanStatusText = ( loan: CapitalLoan ) => { return loan.fully_paid_at ? __( 'Paid off', 'woocommerce-payments' ) + ': ' + - dateI18n( 'M j, Y', loan.fully_paid_at ) + formatDateTimeFromString( loan.fully_paid_at ) : __( 'Active', 'woocommerce-payments' ); }; @@ -112,7 +113,9 @@ const getRowsData = ( loans: CapitalLoan[] ) => const data = { paid_out_at: { value: loan.paid_out_at, - display: clickable( dateI18n( 'M j, Y', loan.paid_out_at ) ), + display: clickable( + formatDateTimeFromString( loan.paid_out_at ) + ), }, status: { value: getLoanStatusText( loan ), @@ -150,7 +153,7 @@ const getRowsData = ( loans: CapitalLoan[] ) => value: loan.first_paydown_at, display: clickable( loan.first_paydown_at - ? dateI18n( 'M j, Y', loan.first_paydown_at ) + ? formatDateTimeFromString( loan.first_paydown_at ) : '-' ), }, @@ -207,6 +210,7 @@ const CapitalPage = (): JSX.Element => { return ( + { wcpaySettings.accountLoans.has_active_loan && ( diff --git a/client/capital/test/__snapshots__/index.test.tsx.snap b/client/capital/test/__snapshots__/index.test.tsx.snap index 2b146dd5714..9f0e93091cf 100644 --- a/client/capital/test/__snapshots__/index.test.tsx.snap +++ b/client/capital/test/__snapshots__/index.test.tsx.snap @@ -5,6 +5,54 @@ exports[`CapitalPage renders the TableCard component with loan data 1`] = `
    +
    + +
    + The date and time formats now match your preferences. You can update them anytime in the + + settings + + . +
    + +
    diff --git a/client/capital/test/index.test.tsx b/client/capital/test/index.test.tsx index f9eb43b8a49..41ea917902a 100644 --- a/client/capital/test/index.test.tsx +++ b/client/capital/test/index.test.tsx @@ -25,6 +25,7 @@ declare const global: { accountLoans: { has_active_loan: boolean; }; + dateFormat: string; }; }; @@ -37,6 +38,7 @@ describe( 'CapitalPage', () => { }, accountLoans: { has_active_loan: true }, testMode: true, + dateFormat: 'M j, Y', }; } ); diff --git a/client/components/account-status/account-fees/expiration-description.js b/client/components/account-status/account-fees/expiration-description.js index 6be5b58681c..497f0207ef3 100644 --- a/client/components/account-status/account-fees/expiration-description.js +++ b/client/components/account-status/account-fees/expiration-description.js @@ -4,13 +4,12 @@ * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { dateI18n } from '@wordpress/date'; -import moment from 'moment'; /** * Internal dependencies */ import { formatCurrency } from 'multi-currency/interface/functions'; +import { formatDateTimeFromString } from 'wcpay/utils/date-time'; const ExpirationDescription = ( { feeData: { volume_allowance: volumeAllowance, end_time: endTime, ...rest }, @@ -26,7 +25,7 @@ const ExpirationDescription = ( { 'woocommerce-payments' ), formatCurrency( volumeAllowance, currencyCode ), - dateI18n( 'F j, Y', moment( endTime ).toISOString() ) + formatDateTimeFromString( endTime ) ); } else if ( volumeAllowance ) { description = sprintf( @@ -44,7 +43,7 @@ const ExpirationDescription = ( { 'Discounted base fee expires on %1$s.', 'woocommerce-payments' ), - dateI18n( 'F j, Y', moment( endTime ).toISOString() ) + formatDateTimeFromString( endTime ) ); } else { return null; diff --git a/client/components/account-status/account-fees/test/index.js b/client/components/account-status/account-fees/test/index.js index 5258af4ffdc..7405b33e371 100644 --- a/client/components/account-status/account-fees/test/index.js +++ b/client/components/account-status/account-fees/test/index.js @@ -46,6 +46,7 @@ describe( 'AccountFees', () => { precision: 2, }, }, + dateFormat: 'F j, Y', }; } ); diff --git a/client/components/active-loan-summary/index.tsx b/client/components/active-loan-summary/index.tsx index 0c5059ef87c..7ae902c590e 100755 --- a/client/components/active-loan-summary/index.tsx +++ b/client/components/active-loan-summary/index.tsx @@ -13,7 +13,6 @@ import { } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { createInterpolateElement } from '@wordpress/element'; -import { dateI18n } from '@wordpress/date'; /** * Internal dependencies. @@ -24,6 +23,7 @@ import { useActiveLoanSummary } from 'wcpay/data'; import { getAdminUrl } from 'wcpay/utils'; import './style.scss'; +import { formatDateTimeFromTimestamp } from 'wcpay/utils/date-time'; const Block = ( { title, @@ -210,12 +210,8 @@ const ActiveLoanSummary = (): JSX.Element => { 'Repaid this period (until %s)', 'woocommerce-payments' ), - dateI18n( - 'M j, Y', - new Date( - details.current_repayment_interval.due_at * - 1000 - ) + formatDateTimeFromTimestamp( + details.current_repayment_interval.due_at ) ) } > @@ -251,9 +247,8 @@ const ActiveLoanSummary = (): JSX.Element => { - { dateI18n( - 'M j, Y', - new Date( details.advance_paid_out_at * 1000 ) + { formatDateTimeFromTimestamp( + details.advance_paid_out_at ) } { - { dateI18n( - 'M j, Y', - new Date( details.repayments_begin_at * 1000 ) + { formatDateTimeFromTimestamp( + details.repayments_begin_at ) } diff --git a/client/components/active-loan-summary/test/__snapshots__/index.js.snap b/client/components/active-loan-summary/test/__snapshots__/index.js.snap index 4424415245c..4e9dd15ec13 100644 --- a/client/components/active-loan-summary/test/__snapshots__/index.js.snap +++ b/client/components/active-loan-summary/test/__snapshots__/index.js.snap @@ -74,7 +74,7 @@ exports[`Active loan summary renders correctly 1`] = `
    - Repaid this period (until Feb 14, 2022) + Repaid this period (until Feb 15, 2022)
    ( { useActiveLoanSummary: 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 + } ), +} ) ); + describe( 'Active loan summary', () => { beforeEach( () => { global.wcpaySettings = { @@ -34,6 +43,7 @@ describe( 'Active loan summary', () => { precision: 2, }, }, + dateFormat: 'M j, Y', }; } ); afterEach( () => { diff --git a/client/components/date-format-notice/index.tsx b/client/components/date-format-notice/index.tsx new file mode 100644 index 00000000000..cdaba000939 --- /dev/null +++ b/client/components/date-format-notice/index.tsx @@ -0,0 +1,67 @@ +/** + * External dependencies + */ +import React, { useState } from 'react'; +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import BannerNotice from 'components/banner-notice'; +import interpolateComponents from '@automattic/interpolate-components'; +import { Link } from '@woocommerce/components'; +import './style.scss'; + +const optionName = 'wcpay_date_format_notice_dismissed'; + +const DateFormatNotice: React.FC = () => { + const { updateOptions } = useDispatch( 'wc/admin/options' ); + const [ isBannerVisible, setIsBannerVisible ] = useState( + ! wcpaySettings.isDateFormatNoticeDismissed + ); + + const handleDismiss = () => { + setIsBannerVisible( false ); + wcpaySettings.isDateFormatNoticeDismissed = true; + updateOptions( { + [ optionName ]: true, + } ); + }; + + const handleSettingsClick = () => { + handleDismiss(); + }; + + if ( ! isBannerVisible ) { + return null; + } + + return ( + + { interpolateComponents( { + components: { + settingsLink: ( + + ), + }, + mixedString: __( + 'The date and time formats now match your preferences. You can update them anytime in the {{settingsLink}}settings{{/settingsLink}}.', + 'woocommerce-payments' + ), + } ) } + + ); +}; + +export default DateFormatNotice; diff --git a/client/components/date-format-notice/style.scss b/client/components/date-format-notice/style.scss new file mode 100644 index 00000000000..86ee0987728 --- /dev/null +++ b/client/components/date-format-notice/style.scss @@ -0,0 +1,5 @@ +.date-format-notice { + .wcpay-banner-notice__content { + align-self: center; // Align the content to the center of the notice and the icon. + } +} diff --git a/client/components/deposits-overview/test/index.tsx b/client/components/deposits-overview/test/index.tsx index edaa068a100..920adf540c7 100644 --- a/client/components/deposits-overview/test/index.tsx +++ b/client/components/deposits-overview/test/index.tsx @@ -68,6 +68,7 @@ declare const global: { connect: { country: string; }; + dateFormat: string; }; }; @@ -240,6 +241,7 @@ describe( 'Deposits Overview information', () => { precision: 2, }, }, + dateFormat: 'F j, Y', }; mockUseDepositIncludesLoan.mockReturnValue( { includesFinancingPayout: false, diff --git a/client/components/disputed-order-notice/index.js b/client/components/disputed-order-notice/index.js index ab51a52d16e..c7d1db9e7d1 100644 --- a/client/components/disputed-order-notice/index.js +++ b/client/components/disputed-order-notice/index.js @@ -1,7 +1,6 @@ import moment from 'moment'; import React, { useEffect } from 'react'; import { __, _n, sprintf } from '@wordpress/i18n'; -import { dateI18n } from '@wordpress/date'; import { createInterpolateElement } from '@wordpress/element'; /** @@ -20,6 +19,7 @@ import { import { useCharge } from 'wcpay/data'; import { recordEvent } from 'tracks'; import './style.scss'; +import { formatDateTimeFromString } from 'wcpay/utils/date-time'; const DisputedOrderNoticeHandler = ( { chargeId, onDisableOrderRefund } ) => { const { data: charge } = useCharge( chargeId ); @@ -84,8 +84,10 @@ const DisputedOrderNoticeHandler = ( { chargeId, onDisableOrderRefund } ) => { return null; } - const now = moment(); - const dueBy = moment.unix( dispute.evidence_details?.due_by ); + // Get current time in UTC for consistent timezone-independent comparison + const now = moment().utc(); + // Parse the Unix timestamp as UTC since it's stored that way in the API + const dueBy = moment.unix( dispute.evidence_details?.due_by ).utc(); // If the dispute is due in the past, don't show notice. if ( ! now.isBefore( dueBy ) ) { @@ -131,7 +133,7 @@ const UrgentDisputeNoticeBody = ( { formatString, formattedAmount, reasons[ disputeReason ].display, - dateI18n( 'M j, Y', dueBy.local().toISOString() ) + formatDateTimeFromString( dueBy.toISOString() ) ); let suffix = sprintf( @@ -182,7 +184,7 @@ const RegularDisputeNoticeBody = ( { const suffix = sprintf( // Translators: %1$s is the dispute due date. __( 'Please respond before %1$s.', 'woocommerce-payments' ), - dateI18n( 'M j, Y', dueBy.local().toISOString() ) + formatDateTimeFromString( dueBy.toISOString() ) ); return ( diff --git a/client/components/disputed-order-notice/test/index.test.js b/client/components/disputed-order-notice/test/index.test.js index 7e44da132e0..784092295f3 100644 --- a/client/components/disputed-order-notice/test/index.test.js +++ b/client/components/disputed-order-notice/test/index.test.js @@ -36,6 +36,7 @@ describe( 'DisputedOrderNoticeHandler', () => { connect: { country: 'US', }, + dateFormat: 'M j, Y', }; useCharge.mockReturnValue( { data: mockCharge } ); } ); diff --git a/client/deposits/details/index.tsx b/client/deposits/details/index.tsx index 2176b079377..4e15a8653a8 100644 --- a/client/deposits/details/index.tsx +++ b/client/deposits/details/index.tsx @@ -4,9 +4,7 @@ * External dependencies */ import React from 'react'; -import { dateI18n } from '@wordpress/date'; import { __, sprintf } from '@wordpress/i18n'; -import moment from 'moment'; import { Card, CardBody, @@ -41,6 +39,8 @@ import { import { depositStatusLabels } from '../strings'; import './style.scss'; import { PayoutsRenameNotice } from '../rename-notice'; +import { formatDateTimeFromString } from 'wcpay/utils/date-time'; +import DateFormatNotice from 'wcpay/components/date-format-notice'; /** * Renders the deposit status indicator UI, re-purposing the OrderStatus component from @woocommerce/components. @@ -135,11 +135,7 @@ export const DepositOverview: React.FC< DepositOverviewProps > = ( { key="depositDate" label={ `${ depositDateLabel }: ` + - 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 ) } value={ } detail={ deposit.bankAccount } @@ -248,6 +244,7 @@ export const DepositDetails: React.FC< DepositDetailsProps > = ( { return ( + { isLoading ? ( diff --git a/client/deposits/details/test/index.tsx b/client/deposits/details/test/index.tsx index 5f350daf4d0..fa9a4dbf042 100644 --- a/client/deposits/details/test/index.tsx +++ b/client/deposits/details/test/index.tsx @@ -45,6 +45,7 @@ declare const global: { connect: { country: string; }; + dateFormat: string; }; wcSettings: { countries: Record< string, string > }; }; @@ -67,6 +68,7 @@ describe( 'Deposit overview', () => { precision: 2, }, }, + dateFormat: 'M j, Y', }; } ); diff --git a/client/deposits/index.tsx b/client/deposits/index.tsx index d799ff3d385..229acdbbb09 100644 --- a/client/deposits/index.tsx +++ b/client/deposits/index.tsx @@ -23,6 +23,7 @@ import { useSettings } from 'wcpay/data'; import DepositsList from './list'; import { hasAutomaticScheduledDeposits } from 'wcpay/deposits/utils'; import { recordEvent } from 'wcpay/tracks'; +import DateFormatNotice from 'wcpay/components/date-format-notice'; const useNextDepositNoticeState = () => { const { updateOptions } = useDispatch( 'wc/admin/options' ); @@ -149,6 +150,7 @@ const DepositsPage: React.FC = () => { return ( + 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`] = `
    +
    + +
    + The date and time formats now match your preferences. You can update them anytime in the + + settings + + . +
    + +
    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;" > +
    + +
    + The date and time formats now match your preferences. You can update them anytime in the + + settings + + . +
    + +
    { 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;" > +
    + +
    + The date and time formats now match your preferences. You can update them anytime in the + + settings + + . +
    + +
    +
    + +
    + The date and time formats now match your preferences. You can update them anytime in the + + settings + + . +
    + +
    { 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 77/83] 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 78/83] 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 79/83] 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 80/83] 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 81/83] 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 2ee25819c1bb26798f00f3e79580350fcb65217a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 22 Dec 2024 12:09:35 +0000 Subject: [PATCH 82/83] Update version and add changelog entries for release 8.7.0 --- changelog.txt | 61 ++++++++++++++++++ changelog/8969-fallback-to-card-payment-type | 5 -- .../add-2253-clickwrap-terms-and-conditions | 4 -- .../add-6924-migrate-test-drive-capabilities | 4 -- changelog/add-9690-recommended-pm | 4 -- .../add-9973-enable-pms-from-capabilities | 4 -- changelog/add-beta-badge-woopay-theme | 5 -- changelog/add-jetpack-config-callback | 4 -- ...imit-woopay-themeing-to-shortcode-checkout | 5 -- .../add-pass-footer-header-styles-to-woopay | 5 -- .../add-woopay-klaviyo-newsletter-support | 4 -- changelog/as-fix-ece-variable-subs | 4 -- changelog/as-fix-ece-variable-subs-free-trial | 4 -- changelog/as-hk-address | 4 -- .../chore-prb-references-in-ece-docs-and-logs | 4 -- ...e-remove-ece-error-assignment-on-loaderror | 5 -- ...emove-tokenized-payment-request-references | 5 -- .../compat-9727-avoid-early-translations | 4 -- ...ve-hooks-from-customer-service-constructor | 4 -- changelog/dev-qit-auth-fix-take-2 | 4 -- changelog/feat-9810-add-seller-message | 4 -- ...nized-ece-product-page-base-implementation | 5 -- changelog/fix-198-mccy-fedex-conversion | 5 -- ...ndle-error-on-refund-during-manual-capture | 4 -- ...atting-arent-respected-in-react-components | 4 -- .../fix-7230-payments-details-mobile-view | 4 -- ...rakedown-tooltip-are-not-internationalised | 4 -- changelog/fix-9114-level3-rounding | 4 -- ...tion-fees-when-transaction-is-not-captured | 4 -- ...ix-9421-auto-enable-woopay-in-sandbox-mode | 4 -- changelog/fix-9612-inquiry-order-note | 4 -- changelog/fix-9716-disputes-api-requests | 4 -- ...5-render-transactions-correctly-on-capture | 4 -- ...ry-payment-activity-transaction-search-css | 4 -- ...42-blank-overview-page-without-wc-features | 4 -- .../fix-9787-woopay-enable-state-settings | 4 -- .../fix-9794-refresh-page-when-ece-dismissed | 4 -- ...-9806-ECE-subscription-checkout-signed-out | 4 -- ...x-9830-browser-error-on-dispute-submission | 4 -- changelog/fix-9889-log-level | 4 -- changelog/fix-9896-postal-code-label | 4 -- changelog/fix-9987-filter-csv-disputes | 4 -- .../fix-9996-currency-conversion-fee-phrasing | 4 -- changelog/fix-add-payment-method-check | 5 -- ...low-addresses-from-woo-supported-countries | 4 -- .../fix-change-woopay-theming-settings-copy | 4 -- .../fix-ece-button-for-price-including-tax | 4 -- changelog/fix-method-title-availability | 4 -- .../fix-php-8-compatibility-errors-warnings | 5 -- .../fix-rounding-error-with-deposit-products | 4 -- changelog/fix-skip-ssl-requirement-env-setup | 4 -- changelog/fix-stripe-link-button | 4 -- .../fix-tokenized-cart-error-notice-json | 5 -- .../fix-tokenized-cart-multiple-variations | 5 -- ...fix-tokenized-cart-subscription-signup-fee | 5 -- ...ed-ece-pay-for-order-without-billing-email | 5 -- .../fix-tokenized-ece-product-bundles-totals | 5 -- changelog/fix-unhandled-promises | 4 -- changelog/fix-upe-country-selection | 4 -- changelog/fix-upe-theme-block | 4 -- changelog/fix-use-effect-console-warning | 5 -- changelog/fix-use-type-is-in-filter | 4 -- changelog/frosso-patch-1 | 4 -- ...checkout-scripts-on-checkout-if-not-loaded | 4 -- changelog/mobile-tpv-tracking-channel | 4 -- changelog/replace-from-url-query | 4 -- changelog/scope-payment-elements-selectors | 4 -- changelog/test-instructions-item-color | 4 -- .../update-1-5316-rename-bank-reference-id | 4 -- ...there-is-an-error-capturing-authorizations | 4 -- ...lude-more-info-on-transaction-details-page | 4 -- changelog/update-7900-payout-notice | 4 -- changelog/update-9910-transaction-id-label | 5 -- .../update-9916-go-live-modal-and-notice | 4 -- .../update-9919-embedded-components-width | 4 -- changelog/update-confirmation-modal-nox | 4 -- changelog/update-jetpack-onboarding-flow | 4 -- ...s-capabilities-to-onboarding-as-get-params | 4 -- ...837-reduce-api-impact-for-test-drive-setup | 5 -- changelog/update-to-standalone-jt | 4 -- package-lock.json | 4 +- package.json | 2 +- readme.txt | 63 ++++++++++++++++++- woocommerce-payments.php | 2 +- 84 files changed, 127 insertions(+), 339 deletions(-) delete mode 100644 changelog/8969-fallback-to-card-payment-type delete mode 100644 changelog/add-2253-clickwrap-terms-and-conditions delete mode 100644 changelog/add-6924-migrate-test-drive-capabilities delete mode 100644 changelog/add-9690-recommended-pm delete mode 100644 changelog/add-9973-enable-pms-from-capabilities delete mode 100644 changelog/add-beta-badge-woopay-theme delete mode 100644 changelog/add-jetpack-config-callback delete mode 100644 changelog/add-limit-woopay-themeing-to-shortcode-checkout delete mode 100644 changelog/add-pass-footer-header-styles-to-woopay delete mode 100644 changelog/add-woopay-klaviyo-newsletter-support delete mode 100644 changelog/as-fix-ece-variable-subs delete mode 100644 changelog/as-fix-ece-variable-subs-free-trial delete mode 100644 changelog/as-hk-address delete mode 100644 changelog/chore-prb-references-in-ece-docs-and-logs delete mode 100644 changelog/chore-remove-ece-error-assignment-on-loaderror delete mode 100644 changelog/chore-remove-tokenized-payment-request-references delete mode 100644 changelog/compat-9727-avoid-early-translations delete mode 100644 changelog/dev-7264-remove-hooks-from-customer-service-constructor delete mode 100644 changelog/dev-qit-auth-fix-take-2 delete mode 100644 changelog/feat-9810-add-seller-message delete mode 100644 changelog/feat-tokenized-ece-product-page-base-implementation delete mode 100644 changelog/fix-198-mccy-fedex-conversion delete mode 100644 changelog/fix-5671-handle-error-on-refund-during-manual-capture delete mode 100644 changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components delete mode 100644 changelog/fix-7230-payments-details-mobile-view delete mode 100644 changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised delete mode 100644 changelog/fix-9114-level3-rounding delete mode 100644 changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured delete mode 100644 changelog/fix-9421-auto-enable-woopay-in-sandbox-mode delete mode 100644 changelog/fix-9612-inquiry-order-note delete mode 100644 changelog/fix-9716-disputes-api-requests delete mode 100644 changelog/fix-9735-render-transactions-correctly-on-capture delete mode 100644 changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css delete mode 100644 changelog/fix-9742-blank-overview-page-without-wc-features delete mode 100644 changelog/fix-9787-woopay-enable-state-settings delete mode 100644 changelog/fix-9794-refresh-page-when-ece-dismissed delete mode 100644 changelog/fix-9806-ECE-subscription-checkout-signed-out delete mode 100644 changelog/fix-9830-browser-error-on-dispute-submission delete mode 100644 changelog/fix-9889-log-level delete mode 100644 changelog/fix-9896-postal-code-label delete mode 100644 changelog/fix-9987-filter-csv-disputes delete mode 100644 changelog/fix-9996-currency-conversion-fee-phrasing delete mode 100644 changelog/fix-add-payment-method-check delete mode 100644 changelog/fix-allow-addresses-from-woo-supported-countries delete mode 100644 changelog/fix-change-woopay-theming-settings-copy delete mode 100644 changelog/fix-ece-button-for-price-including-tax delete mode 100644 changelog/fix-method-title-availability delete mode 100644 changelog/fix-php-8-compatibility-errors-warnings delete mode 100644 changelog/fix-rounding-error-with-deposit-products delete mode 100644 changelog/fix-skip-ssl-requirement-env-setup delete mode 100644 changelog/fix-stripe-link-button delete mode 100644 changelog/fix-tokenized-cart-error-notice-json delete mode 100644 changelog/fix-tokenized-cart-multiple-variations delete mode 100644 changelog/fix-tokenized-cart-subscription-signup-fee delete mode 100644 changelog/fix-tokenized-ece-pay-for-order-without-billing-email delete mode 100644 changelog/fix-tokenized-ece-product-bundles-totals delete mode 100644 changelog/fix-unhandled-promises delete mode 100644 changelog/fix-upe-country-selection delete mode 100644 changelog/fix-upe-theme-block delete mode 100644 changelog/fix-use-effect-console-warning delete mode 100644 changelog/fix-use-type-is-in-filter delete mode 100644 changelog/frosso-patch-1 delete mode 100644 changelog/load-checkout-scripts-on-checkout-if-not-loaded delete mode 100644 changelog/mobile-tpv-tracking-channel delete mode 100644 changelog/replace-from-url-query delete mode 100644 changelog/scope-payment-elements-selectors delete mode 100644 changelog/test-instructions-item-color delete mode 100644 changelog/update-1-5316-rename-bank-reference-id delete mode 100644 changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations delete mode 100644 changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page delete mode 100644 changelog/update-7900-payout-notice delete mode 100644 changelog/update-9910-transaction-id-label delete mode 100644 changelog/update-9916-go-live-modal-and-notice delete mode 100644 changelog/update-9919-embedded-components-width delete mode 100644 changelog/update-confirmation-modal-nox delete mode 100644 changelog/update-jetpack-onboarding-flow delete mode 100644 changelog/update-pass-capabilities-to-onboarding-as-get-params delete mode 100644 changelog/update-s6837-reduce-api-impact-for-test-drive-setup delete mode 100644 changelog/update-to-standalone-jt diff --git a/changelog.txt b/changelog.txt index cc6402cb77c..d6ecbad8a2c 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,66 @@ *** WooPayments Changelog *** += 8.7.0 - 2024-12-25 = +* Add - Add seller_message to failed order notes +* Add - Add WooPay Klaviyo newsletter integration. +* Add - Clickwrap terms and conditions support on WooPay +* Add - Implement gateway method to retrieve recommended payment method. +* Add - Migrate active capabilities from test-drive account when switching to live account. +* Add - Refresh the cart and checkout pages when ECE is dismissed and the shipping options were modified in the payment sheet. +* Fix - Add a rounding entry to Level 3 data for rare cases where rounding errors break calculations. +* Fix - Added conditional use of Jetpack Config callback to avoid i18n notices. +* Fix - Browser error no longer shows after dispute evidence submission +* Fix - Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. +* Fix - Consider WooPay eligibility when retrieving WooPay enable state in the settings. +* Fix - Enable ECE for Virtual Variable Subscriptions with Free Trials. +* Fix - Ensure captured transactions appear in the Transactions tab without requiring a page refresh. +* Fix - Ensure ECE login confirmation dialog is shown on Blocks. +* Fix - Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. +* Fix - Errors were incorrectly marked as info in logs. +* Fix - fix: undefined $cart_contains_subscription +* Fix - Fix blank Payments > Overview page when WC onboarding is disabled. +* Fix - Fixed Affirm using black logo on dark themes +* Fix - Fixed an issue where order metadata was not updated when capturing an order in the processing state. +* Fix - Fixed UPE country detection in Checkout for non-logged in users +* Fix - Fix filtering in async Disputes CSV export +* Fix - Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. +* Fix - Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" +* Fix - Fix payment method filtering when billing country changes in Blocks checkout. +* Fix - Fix styling of transaction details page in mobile view. +* Fix - Hide transaction fee on admin view order screen when transaction is not captured. +* Fix - Load checkout scripts when they are not previously loaded on checkout page. +* Fix - Localize postal code check label based on country. +* Fix - Normalize HK addresses for ECE +* Fix - Order notes for inquiries have clearer content. +* Fix - Performance improvements for Disputes Needing Response task shown in WooCommerce admin. +* Fix - Remove translations during initialization, preventing unnecessary warnings. +* Fix - Restrict Stripe Link to credit card payment method and improve cleanup. +* Fix - Set payment method title once title is known. +* Fix - Show express checkout for products w/o shipping but where tax is included into price. +* Fix - Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. +* Fix - Use "currency conversion fee" instead "foreign exchange fee" in payment timeline and various other places. +* Fix - Use translatable strings on the fee breakdown tooltip of the payment settings screen. +* Update - Add failure reason to failed payments in the timeline. +* Update - Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. +* Update - Adjust the go-live modal to match the latest design. +* Update - Apply User-Defined Date Formatting Settings to WP Admin React Components +* Update - Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. +* Update - chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. +* Update - Ensure more robust selectors scoping to improve theme compatibility. +* Update - Make test instructions copy icon use the same color as the text next to it +* Update - Remove payout timing notice and update the help tooltil on Payments Overview page. +* Update - Update confirmation modal after onbarding +* Update - Update Embedded Components and MOX to support custom width and paddings. +* Update - Update error messages for payment authorization actions to provide more specific and user-friendly feedback. +* Update - Update Jetpack onboarding flow +* Update - WooPay theming copy in the settings page +* Dev - Add support for utilizing NOX capabilities as URL parameters during account creation. +* Dev - Enable Payment Methods preselected by NOX after onboarding accounts +* Dev - Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. +* Dev - Refine verification for disabling ECE on subscriptions that require shipping. +* Dev - Remove hooks from customer and token services to dedicated methods +* Dev - Update the tunelling setup. + = 8.6.1 - 2024-12-17 = * Fix - Checkout: Fix error when wc_address_i18n_params does not have data for a given country * Fix - Skip mysqlcheck SSL Requirement during E2E environment setup diff --git a/changelog/8969-fallback-to-card-payment-type b/changelog/8969-fallback-to-card-payment-type deleted file mode 100644 index ee66dbfa7e7..00000000000 --- a/changelog/8969-fallback-to-card-payment-type +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: update -Comment: Small change to payment method types fallback scenario. - - diff --git a/changelog/add-2253-clickwrap-terms-and-conditions b/changelog/add-2253-clickwrap-terms-and-conditions deleted file mode 100644 index ac0a4ece4b7..00000000000 --- a/changelog/add-2253-clickwrap-terms-and-conditions +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Clickwrap terms and conditions support on WooPay diff --git a/changelog/add-6924-migrate-test-drive-capabilities b/changelog/add-6924-migrate-test-drive-capabilities deleted file mode 100644 index 7b280af4d92..00000000000 --- a/changelog/add-6924-migrate-test-drive-capabilities +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Migrate active capabilities from test-drive account when switching to live account. diff --git a/changelog/add-9690-recommended-pm b/changelog/add-9690-recommended-pm deleted file mode 100644 index 2d615350daa..00000000000 --- a/changelog/add-9690-recommended-pm +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Implement gateway method to retrieve recommended payment method. diff --git a/changelog/add-9973-enable-pms-from-capabilities b/changelog/add-9973-enable-pms-from-capabilities deleted file mode 100644 index 5ecfd2ac5cd..00000000000 --- a/changelog/add-9973-enable-pms-from-capabilities +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Enable Payment Methods preselected by NOX after onboarding accounts diff --git a/changelog/add-beta-badge-woopay-theme b/changelog/add-beta-badge-woopay-theme deleted file mode 100644 index 8d379077613..00000000000 --- a/changelog/add-beta-badge-woopay-theme +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: add -Comment: Adds a label to a gated feature. - - diff --git a/changelog/add-jetpack-config-callback b/changelog/add-jetpack-config-callback deleted file mode 100644 index 64b1a2abb1b..00000000000 --- a/changelog/add-jetpack-config-callback +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Added conditional use of Jetpack Config callback to avoid i18n notices. diff --git a/changelog/add-limit-woopay-themeing-to-shortcode-checkout b/changelog/add-limit-woopay-themeing-to-shortcode-checkout deleted file mode 100644 index 4c089593b1f..00000000000 --- a/changelog/add-limit-woopay-themeing-to-shortcode-checkout +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: add -Comment: Updates the availability criteria of WooPay Global theme-ing feature. This feature is unreleased and behind a feature flag. - - diff --git a/changelog/add-pass-footer-header-styles-to-woopay b/changelog/add-pass-footer-header-styles-to-woopay deleted file mode 100644 index ab6375db250..00000000000 --- a/changelog/add-pass-footer-header-styles-to-woopay +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: add -Comment: Impovements to WooPay themeing, which is not yet released to the public. - - diff --git a/changelog/add-woopay-klaviyo-newsletter-support b/changelog/add-woopay-klaviyo-newsletter-support deleted file mode 100644 index 64e94c6638e..00000000000 --- a/changelog/add-woopay-klaviyo-newsletter-support +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Add WooPay Klaviyo newsletter integration. diff --git a/changelog/as-fix-ece-variable-subs b/changelog/as-fix-ece-variable-subs deleted file mode 100644 index 236497bcab9..00000000000 --- a/changelog/as-fix-ece-variable-subs +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Refine verification for disabling ECE on subscriptions that require shipping. diff --git a/changelog/as-fix-ece-variable-subs-free-trial b/changelog/as-fix-ece-variable-subs-free-trial deleted file mode 100644 index 64d67393c06..00000000000 --- a/changelog/as-fix-ece-variable-subs-free-trial +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Enable ECE for Virtual Variable Subscriptions with Free Trials. diff --git a/changelog/as-hk-address b/changelog/as-hk-address deleted file mode 100644 index d58ddb9ffd9..00000000000 --- a/changelog/as-hk-address +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Normalize HK addresses for ECE diff --git a/changelog/chore-prb-references-in-ece-docs-and-logs b/changelog/chore-prb-references-in-ece-docs-and-logs deleted file mode 100644 index 887525ff7bc..00000000000 --- a/changelog/chore-prb-references-in-ece-docs-and-logs +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. diff --git a/changelog/chore-remove-ece-error-assignment-on-loaderror b/changelog/chore-remove-ece-error-assignment-on-loaderror deleted file mode 100644 index cce991d09ba..00000000000 --- a/changelog/chore-remove-ece-error-assignment-on-loaderror +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: update -Comment: chore: remove ECE error assignment on loaderror - - diff --git a/changelog/chore-remove-tokenized-payment-request-references b/changelog/chore-remove-tokenized-payment-request-references deleted file mode 100644 index 56dc3b0a0cc..00000000000 --- a/changelog/chore-remove-tokenized-payment-request-references +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: chore: remove tokeinzed payment request code - - diff --git a/changelog/compat-9727-avoid-early-translations b/changelog/compat-9727-avoid-early-translations deleted file mode 100644 index 51432b8cd10..00000000000 --- a/changelog/compat-9727-avoid-early-translations +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Remove translations during initialization, preventing unnecessary warnings. diff --git a/changelog/dev-7264-remove-hooks-from-customer-service-constructor b/changelog/dev-7264-remove-hooks-from-customer-service-constructor deleted file mode 100644 index d912717fc31..00000000000 --- a/changelog/dev-7264-remove-hooks-from-customer-service-constructor +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: dev - -Remove hooks from customer and token services to dedicated methods diff --git a/changelog/dev-qit-auth-fix-take-2 b/changelog/dev-qit-auth-fix-take-2 deleted file mode 100644 index 67ec99abd79..00000000000 --- a/changelog/dev-qit-auth-fix-take-2 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. diff --git a/changelog/feat-9810-add-seller-message b/changelog/feat-9810-add-seller-message deleted file mode 100644 index 2669c24015b..00000000000 --- a/changelog/feat-9810-add-seller-message +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: add - -Add seller_message to failed order notes diff --git a/changelog/feat-tokenized-ece-product-page-base-implementation b/changelog/feat-tokenized-ece-product-page-base-implementation deleted file mode 100644 index e0f342c1623..00000000000 --- a/changelog/feat-tokenized-ece-product-page-base-implementation +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: update -Comment: feat: tokenized ECE product page base implementation - - diff --git a/changelog/fix-198-mccy-fedex-conversion b/changelog/fix-198-mccy-fedex-conversion deleted file mode 100644 index 7fecbc49b87..00000000000 --- a/changelog/fix-198-mccy-fedex-conversion +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: Fix FedEx insurance rates with different currencies. - - diff --git a/changelog/fix-5671-handle-error-on-refund-during-manual-capture b/changelog/fix-5671-handle-error-on-refund-during-manual-capture deleted file mode 100644 index 016c68f13aa..00000000000 --- a/changelog/fix-5671-handle-error-on-refund-during-manual-capture +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fixed an issue where order metadata was not updated when capturing an order in the processing state. diff --git a/changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components b/changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components deleted file mode 100644 index 5c69920cf26..00000000000 --- a/changelog/fix-6567-user-set-date-and-time-formatting-arent-respected-in-react-components +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Apply User-Defined Date Formatting Settings to WP Admin React Components diff --git a/changelog/fix-7230-payments-details-mobile-view b/changelog/fix-7230-payments-details-mobile-view deleted file mode 100644 index 93e179a44ca..00000000000 --- a/changelog/fix-7230-payments-details-mobile-view +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix styling of transaction details page in mobile view. diff --git a/changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised b/changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised deleted file mode 100644 index 57528510958..00000000000 --- a/changelog/fix-8620-fee-types-in-fees-brakedown-tooltip-are-not-internationalised +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Use translatable strings on the fee breakdown tooltip of the payment settings screen. diff --git a/changelog/fix-9114-level3-rounding b/changelog/fix-9114-level3-rounding deleted file mode 100644 index 713c8d684cc..00000000000 --- a/changelog/fix-9114-level3-rounding +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Add a rounding entry to Level 3 data for rare cases where rounding errors break calculations. diff --git a/changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured b/changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured deleted file mode 100644 index f524fd812f1..00000000000 --- a/changelog/fix-9418-hide-transaction-fees-when-transaction-is-not-captured +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Hide transaction fee on admin view order screen when transaction is not captured. diff --git a/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode b/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode deleted file mode 100644 index 30ec0c7fed5..00000000000 --- a/changelog/fix-9421-auto-enable-woopay-in-sandbox-mode +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. diff --git a/changelog/fix-9612-inquiry-order-note b/changelog/fix-9612-inquiry-order-note deleted file mode 100644 index 3fce0a23430..00000000000 --- a/changelog/fix-9612-inquiry-order-note +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Order notes for inquiries have clearer content. diff --git a/changelog/fix-9716-disputes-api-requests b/changelog/fix-9716-disputes-api-requests deleted file mode 100644 index 10f5387c9b4..00000000000 --- a/changelog/fix-9716-disputes-api-requests +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Performance improvements for Disputes Needing Response task shown in WooCommerce admin. diff --git a/changelog/fix-9735-render-transactions-correctly-on-capture b/changelog/fix-9735-render-transactions-correctly-on-capture deleted file mode 100644 index 9ed7f628e4c..00000000000 --- a/changelog/fix-9735-render-transactions-correctly-on-capture +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ensure captured transactions appear in the Transactions tab without requiring a page refresh. diff --git a/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css b/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css deleted file mode 100644 index 3841ea6164e..00000000000 --- a/changelog/fix-9736-remove-temporary-payment-activity-transaction-search-css +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. diff --git a/changelog/fix-9742-blank-overview-page-without-wc-features b/changelog/fix-9742-blank-overview-page-without-wc-features deleted file mode 100644 index 8d473bd8ab4..00000000000 --- a/changelog/fix-9742-blank-overview-page-without-wc-features +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix blank Payments > Overview page when WC onboarding is disabled. diff --git a/changelog/fix-9787-woopay-enable-state-settings b/changelog/fix-9787-woopay-enable-state-settings deleted file mode 100644 index cee183680df..00000000000 --- a/changelog/fix-9787-woopay-enable-state-settings +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Consider WooPay eligibility when retrieving WooPay enable state in the settings. diff --git a/changelog/fix-9794-refresh-page-when-ece-dismissed b/changelog/fix-9794-refresh-page-when-ece-dismissed deleted file mode 100644 index 7ec81b4760e..00000000000 --- a/changelog/fix-9794-refresh-page-when-ece-dismissed +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: add - -Refresh the cart and checkout pages when ECE is dismissed and the shipping options were modified in the payment sheet. diff --git a/changelog/fix-9806-ECE-subscription-checkout-signed-out b/changelog/fix-9806-ECE-subscription-checkout-signed-out deleted file mode 100644 index fa25afd1f10..00000000000 --- a/changelog/fix-9806-ECE-subscription-checkout-signed-out +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ensure ECE login confirmation dialog is shown on Blocks. diff --git a/changelog/fix-9830-browser-error-on-dispute-submission b/changelog/fix-9830-browser-error-on-dispute-submission deleted file mode 100644 index 918ad744351..00000000000 --- a/changelog/fix-9830-browser-error-on-dispute-submission +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Browser error no longer shows after dispute evidence submission diff --git a/changelog/fix-9889-log-level b/changelog/fix-9889-log-level deleted file mode 100644 index d2f54e24c1a..00000000000 --- a/changelog/fix-9889-log-level +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Errors were incorrectly marked as info in logs. diff --git a/changelog/fix-9896-postal-code-label b/changelog/fix-9896-postal-code-label deleted file mode 100644 index dad971912a8..00000000000 --- a/changelog/fix-9896-postal-code-label +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Localize postal code check label based on country. diff --git a/changelog/fix-9987-filter-csv-disputes b/changelog/fix-9987-filter-csv-disputes deleted file mode 100644 index e4a87b24b1b..00000000000 --- a/changelog/fix-9987-filter-csv-disputes +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix filtering in async Disputes CSV export diff --git a/changelog/fix-9996-currency-conversion-fee-phrasing b/changelog/fix-9996-currency-conversion-fee-phrasing deleted file mode 100644 index bdee2cbc00f..00000000000 --- a/changelog/fix-9996-currency-conversion-fee-phrasing +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Use "currency conversion fee" instead "foreign exchange fee" in payment timeline and various other places. diff --git a/changelog/fix-add-payment-method-check b/changelog/fix-add-payment-method-check deleted file mode 100644 index 4ffc9e6342f..00000000000 --- a/changelog/fix-add-payment-method-check +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: Added a check for the gateway id before comparing it - - diff --git a/changelog/fix-allow-addresses-from-woo-supported-countries b/changelog/fix-allow-addresses-from-woo-supported-countries deleted file mode 100644 index 626fd1ce34f..00000000000 --- a/changelog/fix-allow-addresses-from-woo-supported-countries +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Checkout: Fix error when wc_address_i18n_params does not have data for a given country diff --git a/changelog/fix-change-woopay-theming-settings-copy b/changelog/fix-change-woopay-theming-settings-copy deleted file mode 100644 index fa73b3672f8..00000000000 --- a/changelog/fix-change-woopay-theming-settings-copy +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -WooPay theming copy in the settings page diff --git a/changelog/fix-ece-button-for-price-including-tax b/changelog/fix-ece-button-for-price-including-tax deleted file mode 100644 index 521ceb2af68..00000000000 --- a/changelog/fix-ece-button-for-price-including-tax +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Show express checkout for products w/o shipping but where tax is included into price. diff --git a/changelog/fix-method-title-availability b/changelog/fix-method-title-availability deleted file mode 100644 index d9d2a0c0217..00000000000 --- a/changelog/fix-method-title-availability +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Set payment method title once title is known. diff --git a/changelog/fix-php-8-compatibility-errors-warnings b/changelog/fix-php-8-compatibility-errors-warnings deleted file mode 100644 index 9c393f71654..00000000000 --- a/changelog/fix-php-8-compatibility-errors-warnings +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: These changes fix some PHP compatibility errors that don't impact WooPayments behaviour. - - diff --git a/changelog/fix-rounding-error-with-deposit-products b/changelog/fix-rounding-error-with-deposit-products deleted file mode 100644 index d42215e3919..00000000000 --- a/changelog/fix-rounding-error-with-deposit-products +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. diff --git a/changelog/fix-skip-ssl-requirement-env-setup b/changelog/fix-skip-ssl-requirement-env-setup deleted file mode 100644 index 691f98adbfa..00000000000 --- a/changelog/fix-skip-ssl-requirement-env-setup +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Skip mysqlcheck SSL Requirement during E2E environment setup diff --git a/changelog/fix-stripe-link-button b/changelog/fix-stripe-link-button deleted file mode 100644 index d8acf0626f1..00000000000 --- a/changelog/fix-stripe-link-button +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Restrict Stripe Link to credit card payment method and improve cleanup. diff --git a/changelog/fix-tokenized-cart-error-notice-json b/changelog/fix-tokenized-cart-error-notice-json deleted file mode 100644 index c132e0f7eeb..00000000000 --- a/changelog/fix-tokenized-cart-error-notice-json +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: fix: tokenized cart error notice json - - diff --git a/changelog/fix-tokenized-cart-multiple-variations b/changelog/fix-tokenized-cart-multiple-variations deleted file mode 100644 index 5d155cd5513..00000000000 --- a/changelog/fix-tokenized-cart-multiple-variations +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: fix: tokenized cart & multiple variations. - - diff --git a/changelog/fix-tokenized-cart-subscription-signup-fee b/changelog/fix-tokenized-cart-subscription-signup-fee deleted file mode 100644 index 5abe9f0226b..00000000000 --- a/changelog/fix-tokenized-cart-subscription-signup-fee +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: fix: tokenized cart subscription signup fee price - - diff --git a/changelog/fix-tokenized-ece-pay-for-order-without-billing-email b/changelog/fix-tokenized-ece-pay-for-order-without-billing-email deleted file mode 100644 index 6e34989b976..00000000000 --- a/changelog/fix-tokenized-ece-pay-for-order-without-billing-email +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: fix: tokenized ECE do not show button when missing billing email - - diff --git a/changelog/fix-tokenized-ece-product-bundles-totals b/changelog/fix-tokenized-ece-product-bundles-totals deleted file mode 100644 index c003feec46a..00000000000 --- a/changelog/fix-tokenized-ece-product-bundles-totals +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: fix: tokenized ECE item compatibility w/ product bundles - - diff --git a/changelog/fix-unhandled-promises b/changelog/fix-unhandled-promises deleted file mode 100644 index a4d1a679405..00000000000 --- a/changelog/fix-unhandled-promises +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Fix payment method filtering when billing country changes in Blocks checkout. diff --git a/changelog/fix-upe-country-selection b/changelog/fix-upe-country-selection deleted file mode 100644 index 478ffa1cfcd..00000000000 --- a/changelog/fix-upe-country-selection +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fixed UPE country detection in Checkout for non-logged in users diff --git a/changelog/fix-upe-theme-block b/changelog/fix-upe-theme-block deleted file mode 100644 index 6afa59f04d3..00000000000 --- a/changelog/fix-upe-theme-block +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fixed Affirm using black logo on dark themes diff --git a/changelog/fix-use-effect-console-warning b/changelog/fix-use-effect-console-warning deleted file mode 100644 index 45219e7b39a..00000000000 --- a/changelog/fix-use-effect-console-warning +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: dev -Comment: fix: console warning on plugins page - - diff --git a/changelog/fix-use-type-is-in-filter b/changelog/fix-use-type-is-in-filter deleted file mode 100644 index 3639b203c36..00000000000 --- a/changelog/fix-use-type-is-in-filter +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. diff --git a/changelog/frosso-patch-1 b/changelog/frosso-patch-1 deleted file mode 100644 index e3812625698..00000000000 --- a/changelog/frosso-patch-1 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -fix: undefined $cart_contains_subscription diff --git a/changelog/load-checkout-scripts-on-checkout-if-not-loaded b/changelog/load-checkout-scripts-on-checkout-if-not-loaded deleted file mode 100644 index 4a684203a2e..00000000000 --- a/changelog/load-checkout-scripts-on-checkout-if-not-loaded +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Load checkout scripts when they are not previously loaded on checkout page. diff --git a/changelog/mobile-tpv-tracking-channel b/changelog/mobile-tpv-tracking-channel deleted file mode 100644 index a7b990214df..00000000000 --- a/changelog/mobile-tpv-tracking-channel +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. diff --git a/changelog/replace-from-url-query b/changelog/replace-from-url-query deleted file mode 100644 index 58688e1c42f..00000000000 --- a/changelog/replace-from-url-query +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: fix - -Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" diff --git a/changelog/scope-payment-elements-selectors b/changelog/scope-payment-elements-selectors deleted file mode 100644 index 515bb60dc2e..00000000000 --- a/changelog/scope-payment-elements-selectors +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Ensure more robust selectors scoping to improve theme compatibility. diff --git a/changelog/test-instructions-item-color b/changelog/test-instructions-item-color deleted file mode 100644 index 4bf5983e8e6..00000000000 --- a/changelog/test-instructions-item-color +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Make test instructions copy icon use the same color as the text next to it diff --git a/changelog/update-1-5316-rename-bank-reference-id b/changelog/update-1-5316-rename-bank-reference-id deleted file mode 100644 index 0a2841c0ad9..00000000000 --- a/changelog/update-1-5316-rename-bank-reference-id +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. diff --git a/changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations b/changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations deleted file mode 100644 index b76d70e1cf9..00000000000 --- a/changelog/update-5002-authorizations-improve-message-shown-to-merchants-when-there-is-an-error-capturing-authorizations +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update error messages for payment authorization actions to provide more specific and user-friendly feedback. diff --git a/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page b/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page deleted file mode 100644 index daf90a1cd39..00000000000 --- a/changelog/update-5713-failed-orders-should-include-more-info-on-transaction-details-page +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Add failure reason to failed payments in the timeline. diff --git a/changelog/update-7900-payout-notice b/changelog/update-7900-payout-notice deleted file mode 100644 index 4a49df73e41..00000000000 --- a/changelog/update-7900-payout-notice +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Remove payout timing notice and update the help tooltil on Payments Overview page. diff --git a/changelog/update-9910-transaction-id-label b/changelog/update-9910-transaction-id-label deleted file mode 100644 index 0e43652d02b..00000000000 --- a/changelog/update-9910-transaction-id-label +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: fix -Comment: Change ID to uppercase in the 'Transaction ID' column label for consistency with similar unique IDs in the UI. - - diff --git a/changelog/update-9916-go-live-modal-and-notice b/changelog/update-9916-go-live-modal-and-notice deleted file mode 100644 index 789b36753a9..00000000000 --- a/changelog/update-9916-go-live-modal-and-notice +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Adjust the go-live modal to match the latest design. diff --git a/changelog/update-9919-embedded-components-width b/changelog/update-9919-embedded-components-width deleted file mode 100644 index ca8fe89ebb7..00000000000 --- a/changelog/update-9919-embedded-components-width +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: update - -Update Embedded Components and MOX to support custom width and paddings. diff --git a/changelog/update-confirmation-modal-nox b/changelog/update-confirmation-modal-nox deleted file mode 100644 index 0ffd1af6127..00000000000 --- a/changelog/update-confirmation-modal-nox +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Update confirmation modal after onbarding diff --git a/changelog/update-jetpack-onboarding-flow b/changelog/update-jetpack-onboarding-flow deleted file mode 100644 index a28c6ac383c..00000000000 --- a/changelog/update-jetpack-onboarding-flow +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: update - -Update Jetpack onboarding flow diff --git a/changelog/update-pass-capabilities-to-onboarding-as-get-params b/changelog/update-pass-capabilities-to-onboarding-as-get-params deleted file mode 100644 index 9104e7a8f99..00000000000 --- a/changelog/update-pass-capabilities-to-onboarding-as-get-params +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Add support for utilizing NOX capabilities as URL parameters during account creation. diff --git a/changelog/update-s6837-reduce-api-impact-for-test-drive-setup b/changelog/update-s6837-reduce-api-impact-for-test-drive-setup deleted file mode 100644 index eaa0360fe7a..00000000000 --- a/changelog/update-s6837-reduce-api-impact-for-test-drive-setup +++ /dev/null @@ -1,5 +0,0 @@ -Significance: patch -Type: update -Comment: Reduce the maximum number of API calls during the test-drive process and add maximum duration cut off logic. - - diff --git a/changelog/update-to-standalone-jt b/changelog/update-to-standalone-jt deleted file mode 100644 index 4df87f235ec..00000000000 --- a/changelog/update-to-standalone-jt +++ /dev/null @@ -1,4 +0,0 @@ -Significance: minor -Type: dev - -Update the tunelling setup. diff --git a/package-lock.json b/package-lock.json index 083ed1adf12..9df1527f16d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "woocommerce-payments", - "version": "8.6.1", + "version": "8.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "woocommerce-payments", - "version": "8.6.1", + "version": "8.7.0", "hasInstallScript": true, "license": "GPL-3.0-or-later", "dependencies": { diff --git a/package.json b/package.json index f634378e064..f177e2babbb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-payments", - "version": "8.6.1", + "version": "8.7.0", "main": "webpack.config.js", "author": "Automattic", "license": "GPL-3.0-or-later", diff --git a/readme.txt b/readme.txt index 0122e5021ae..cf22809f3b1 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.7 Requires PHP: 7.3 -Stable tag: 8.6.1 +Stable tag: 8.7.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -94,6 +94,67 @@ Please note that our support for the checkout block is still experimental and th == Changelog == += 8.7.0 - 2024-12-25 = +* Add - Add seller_message to failed order notes +* Add - Add WooPay Klaviyo newsletter integration. +* Add - Clickwrap terms and conditions support on WooPay +* Add - Implement gateway method to retrieve recommended payment method. +* Add - Migrate active capabilities from test-drive account when switching to live account. +* Add - Refresh the cart and checkout pages when ECE is dismissed and the shipping options were modified in the payment sheet. +* Fix - Add a rounding entry to Level 3 data for rare cases where rounding errors break calculations. +* Fix - Added conditional use of Jetpack Config callback to avoid i18n notices. +* Fix - Browser error no longer shows after dispute evidence submission +* Fix - Ceil product prices after applying currency conversion, but before charm pricing and price rounding from settings is applied. +* Fix - Consider WooPay eligibility when retrieving WooPay enable state in the settings. +* Fix - Enable ECE for Virtual Variable Subscriptions with Free Trials. +* Fix - Ensure captured transactions appear in the Transactions tab without requiring a page refresh. +* Fix - Ensure ECE login confirmation dialog is shown on Blocks. +* Fix - Ensure WooPay 'enabled by default' value is correctly set in sandbox mode. +* Fix - Errors were incorrectly marked as info in logs. +* Fix - fix: undefined $cart_contains_subscription +* Fix - Fix blank Payments > Overview page when WC onboarding is disabled. +* Fix - Fixed Affirm using black logo on dark themes +* Fix - Fixed an issue where order metadata was not updated when capturing an order in the processing state. +* Fix - Fixed UPE country detection in Checkout for non-logged in users +* Fix - Fix filtering in async Disputes CSV export +* Fix - Fix inconsistent alignment of the download button across transactions, payouts, and disputes reporting views for a more cohesive user interface. +* Fix - Fix Jetpack onboarding URL query from "woocommerce-payments" to "woocommerce-core-profiler" +* Fix - Fix payment method filtering when billing country changes in Blocks checkout. +* Fix - Fix styling of transaction details page in mobile view. +* Fix - Hide transaction fee on admin view order screen when transaction is not captured. +* Fix - Load checkout scripts when they are not previously loaded on checkout page. +* Fix - Localize postal code check label based on country. +* Fix - Normalize HK addresses for ECE +* Fix - Order notes for inquiries have clearer content. +* Fix - Performance improvements for Disputes Needing Response task shown in WooCommerce admin. +* Fix - Remove translations during initialization, preventing unnecessary warnings. +* Fix - Restrict Stripe Link to credit card payment method and improve cleanup. +* Fix - Set payment method title once title is known. +* Fix - Show express checkout for products w/o shipping but where tax is included into price. +* Fix - Support 'type_is_in' filter for Transactions list report, to allow easy filtering by multiple types. +* Fix - Use "currency conversion fee" instead "foreign exchange fee" in payment timeline and various other places. +* Fix - Use translatable strings on the fee breakdown tooltip of the payment settings screen. +* Update - Add failure reason to failed payments in the timeline. +* Update - Add support for showing `In-Person (POS)` as the transaction channel for mobile POS transactions in wp-admin Payments, enhancing visibility in both transaction lists and details. +* Update - Adjust the go-live modal to match the latest design. +* Update - Apply User-Defined Date Formatting Settings to WP Admin React Components +* Update - Change 'Bank reference key' label to 'Bank reference ID' in Payouts list column for consistency. +* Update - chore: renamed PRB references in GooglePay/ApplePay implementation docs and logs files to ECE. +* Update - Ensure more robust selectors scoping to improve theme compatibility. +* Update - Make test instructions copy icon use the same color as the text next to it +* Update - Remove payout timing notice and update the help tooltil on Payments Overview page. +* Update - Update confirmation modal after onbarding +* Update - Update Embedded Components and MOX to support custom width and paddings. +* Update - Update error messages for payment authorization actions to provide more specific and user-friendly feedback. +* Update - Update Jetpack onboarding flow +* Update - WooPay theming copy in the settings page +* Dev - Add support for utilizing NOX capabilities as URL parameters during account creation. +* Dev - Enable Payment Methods preselected by NOX after onboarding accounts +* Dev - Fixing issue with parsing QIT authentication.Fixing issue with parsing QIT authentication. +* Dev - Refine verification for disabling ECE on subscriptions that require shipping. +* Dev - Remove hooks from customer and token services to dedicated methods +* Dev - Update the tunelling setup. + = 8.6.1 - 2024-12-17 = * Fix - Checkout: Fix error when wc_address_i18n_params does not have data for a given country * Fix - Skip mysqlcheck SSL Requirement during E2E environment setup diff --git a/woocommerce-payments.php b/woocommerce-payments.php index c606a36f8b9..5a8f4fb0485 100644 --- a/woocommerce-payments.php +++ b/woocommerce-payments.php @@ -11,7 +11,7 @@ * WC tested up to: 9.4.0 * Requires at least: 6.0 * Requires PHP: 7.3 - * Version: 8.6.1 + * Version: 8.7.0 * Requires Plugins: woocommerce * * @package WooCommerce\Payments From 7551fb257b792c2593246ab88678574932dfe435 Mon Sep 17 00:00:00 2001 From: Taha Paksu <3295+tpaksu@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:00:31 +0300 Subject: [PATCH 83/83] Change stable tag to 8.6.1 --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index cf22809f3b1..b94d68e854d 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.7 Requires PHP: 7.3 -Stable tag: 8.7.0 +Stable tag: 8.6.1 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html