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/' ) );