Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: add

Add backend support for additional dispute evidence types (event, booking, other) behind feature flag.
13 changes: 13 additions & 0 deletions includes/class-wc-payments-features.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class WC_Payments_Features {
const WOOPAY_FIRST_PARTY_AUTH_FLAG_NAME = '_wcpay_feature_woopay_first_party_auth';
const WOOPAY_DIRECT_CHECKOUT_FLAG_NAME = '_wcpay_feature_woopay_direct_checkout';
const DISPUTE_ISSUER_EVIDENCE = '_wcpay_feature_dispute_issuer_evidence';
const DISPUTE_ADDITIONAL_EVIDENCE_TYPES = '_wcpay_feature_dispute_additional_evidence_types';
const WOOPAY_GLOBAL_THEME_SUPPORT_FLAG_NAME = '_wcpay_feature_woopay_global_theme_support';
const WCPAY_DYNAMIC_CHECKOUT_PLACE_ORDER_BUTTON_FLAG_NAME = '_wcpay_feature_dynamic_checkout_place_order_button';
const ACCOUNT_DETAILS_FLAG_NAME = '_wcpay_feature_account_details';
Expand Down Expand Up @@ -324,6 +325,17 @@ public static function is_dispute_issuer_evidence_enabled(): bool {
return '1' === get_option( self::DISPUTE_ISSUER_EVIDENCE, '0' );
}

/**
* Checks whether Dispute Additional Evidence Types feature should be enabled. Disabled by default.
*
* This gates the new evidence form types (event, booking_reservation, other) for dispute challenges.
*
* @return bool
*/
public static function is_dispute_additional_evidence_types_enabled(): bool {
return '1' === get_option( self::DISPUTE_ADDITIONAL_EVIDENCE_TYPES, '0' );
}

/**
* Checks whether the next deposit notice on the deposits list screen has been dismissed.
*
Expand Down Expand Up @@ -373,6 +385,7 @@ public static function to_array() {
'documents' => self::is_documents_section_enabled(),
'woopayExpressCheckout' => self::is_woopay_express_checkout_enabled(),
'isDisputeIssuerEvidenceEnabled' => self::is_dispute_issuer_evidence_enabled(),
'isDisputeAdditionalEvidenceTypesEnabled' => self::is_dispute_additional_evidence_types_enabled(),
'isFRTReviewFeatureActive' => self::is_frt_review_feature_active(),
'isDynamicCheckoutPlaceOrderButtonEnabled' => self::is_dynamic_checkout_place_order_button_enabled(),
'isAccountDetailsEnabled' => self::is_account_details_enabled(),
Expand Down
28 changes: 21 additions & 7 deletions includes/wc-payment-api/class-wc-payments-api-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2962,9 +2962,9 @@ private function determine_suggested_product_type( WC_Order $order ): string {
return 'physical_product';
}

$virtual_products = 0;
$physical_products = 0;
$product_count = 0;
$virtual_products = 0;
$product_count = 0;
$product_type = null;

foreach ( $items as $item ) {
// Only process product items.
Expand All @@ -2979,20 +2979,34 @@ private function determine_suggested_product_type( WC_Order $order ): string {

++$product_count;

// Capture first product's type (only used for single-product orders).
if ( null === $product_type ) {
$product_type = $product->get_type();
}

if ( $product->is_virtual() ) {
++$virtual_products;
} else {
++$physical_products;
}
}

// If no valid products found, default to physical.
if ( 0 === $product_count ) {
return 'physical_product';
}

// If more than one product, suggest multiple.
if ( $product_count > 1 ) {
return 'multiple';
}

// If only one product and it's virtual, suggest digital.
if ( 1 === $product_count && 1 === $virtual_products ) {
// At this point, we know there's exactly one product.
// Check for specific product types (gated by feature flag).
if ( WC_Payments_Features::is_dispute_additional_evidence_types_enabled() && 'booking' === $product_type ) {
return 'booking_reservation';
}

// Check if it's virtual (digital product or service).
if ( 1 === $virtual_products ) {
return 'digital_product_or_service';
}

Expand Down
68 changes: 55 additions & 13 deletions tests/unit/wc-payment-api/test-class-wc-payments-api-client.php
Original file line number Diff line number Diff line change
Expand Up @@ -1304,7 +1304,10 @@ private function create_woocommerce_default_pages(): array {
*
* @dataProvider data_determine_suggested_product_type
*/
public function test_determine_suggested_product_type( $order_items, $expected_product_type ) {
public function test_determine_suggested_product_type( $order_items, $expected_product_type, $feature_flag_enabled = true ) {
// Set the feature flag option.
update_option( WC_Payments_Features::DISPUTE_ADDITIONAL_EVIDENCE_TYPES, $feature_flag_enabled ? '1' : '0' );

// Create a mock order.
$mock_order = $this->getMockBuilder( 'WC_Order' )
->disableOriginalConstructor()
Expand All @@ -1328,73 +1331,112 @@ public function test_determine_suggested_product_type( $order_items, $expected_p
*/
public function data_determine_suggested_product_type() {
return [
'empty_order' => [
'empty_order' => [
'order_items' => [],
'expected_product_type' => 'physical_product',
],
'single_physical_product' => [
'single_physical_product' => [
'order_items' => [
$this->create_mock_order_item_product( false ), // not virtual.
],
'expected_product_type' => 'physical_product',
],
'single_virtual_product' => [
'single_virtual_product' => [
'order_items' => [
$this->create_mock_order_item_product( true ), // virtual.
],
'expected_product_type' => 'digital_product_or_service',
],
'multiple_products_mixed' => [
'multiple_products_mixed' => [
'order_items' => [
$this->create_mock_order_item_product( false ), // physical.
$this->create_mock_order_item_product( true ), // virtual.
],
'expected_product_type' => 'multiple',
],
'multiple_physical_products' => [
'multiple_physical_products' => [
'order_items' => [
$this->create_mock_order_item_product( false ), // physical.
$this->create_mock_order_item_product( false ), // physical.
],
'expected_product_type' => 'multiple',
],
'multiple_virtual_products' => [
'multiple_virtual_products' => [
'order_items' => [
$this->create_mock_order_item_product( true ), // virtual.
$this->create_mock_order_item_product( true ), // virtual.
],
'expected_product_type' => 'multiple',
],
'order_with_non_product_items' => [
'order_with_non_product_items' => [
'order_items' => [
$this->create_mock_order_item_product( true ), // virtual product.
$this->create_mock_order_item_shipping(), // shipping item (not a product).
],
'expected_product_type' => 'digital_product_or_service',
],
'order_with_invalid_product' => [
'order_with_invalid_product' => [
'order_items' => [
$this->create_mock_order_item_product( true, false ), // virtual but invalid product.
],
'expected_product_type' => 'physical_product',
],
'single_booking_product' => [
'order_items' => [
$this->create_mock_order_item_product( true, true, 'booking' ), // booking product.
],
'expected_product_type' => 'booking_reservation',
'feature_flag_enabled' => true,
],
'single_booking_product_flag_off' => [
'order_items' => [
$this->create_mock_order_item_product( true, true, 'booking' ), // booking product (virtual).
],
'expected_product_type' => 'digital_product_or_service', // Falls back to virtual detection.
'feature_flag_enabled' => false,
],
'single_booking_product_physical_flag_off' => [
'order_items' => [
$this->create_mock_order_item_product( false, true, 'booking' ), // booking product (not virtual).
],
'expected_product_type' => 'physical_product', // Falls back to physical detection.
'feature_flag_enabled' => false,
],
'multiple_booking_products' => [
'order_items' => [
$this->create_mock_order_item_product( true, true, 'booking' ), // booking.
$this->create_mock_order_item_product( true, true, 'booking' ), // booking.
],
'expected_product_type' => 'multiple',
'feature_flag_enabled' => true,
],
'booking_physical_mixed' => [
'order_items' => [
$this->create_mock_order_item_product( true, true, 'booking' ), // booking.
$this->create_mock_order_item_product( false, true, 'simple' ), // physical.
],
'expected_product_type' => 'multiple',
'feature_flag_enabled' => true,
],
];
}

/**
* Create a mock order item product for testing.
*
* @param bool $is_virtual Whether the product is virtual.
* @param bool $is_valid Whether the product is valid (can be retrieved).
* @param bool $is_virtual Whether the product is virtual.
* @param bool $is_valid Whether the product is valid (can be retrieved).
* @param string $product_type The product type (e.g., 'simple', 'booking', 'variable').
* @return MockObject
*/
private function create_mock_order_item_product( $is_virtual = false, $is_valid = true ) {
private function create_mock_order_item_product( $is_virtual = false, $is_valid = true, $product_type = 'simple' ) {
$mock_product = $this->getMockBuilder( 'WC_Product' )
->disableOriginalConstructor()
->setMethods( [ 'is_virtual' ] )
->setMethods( [ 'is_virtual', 'get_type' ] )
->getMock();

$mock_product->method( 'is_virtual' )->willReturn( $is_virtual );
$mock_product->method( 'get_type' )->willReturn( $product_type );

$mock_order_item = $this->getMockBuilder( 'WC_Order_Item_Product' )
->disableOriginalConstructor()
Expand Down
Loading