Merge branch 'trunk' into PCP-5009-phase-1-things-to-do-next-action-item

This commit is contained in:
Narek Zakarian 2025-08-05 11:37:27 +04:00
commit ca86cb83ae
No known key found for this signature in database
GPG key ID: 07AFD7E7A9C164A7
14 changed files with 339 additions and 10 deletions

View file

@ -1,8 +1,25 @@
*** Changelog ***
= 3.0.9 - 2025-07-31 =
* Fix - Payment via "Proceed to PayPal" may result in a redirect loop #3570
= 3.0.8 - 2025-07-28 =
* Enhancement - Migration from Legacy Settings to New Settings as opt-in via banner & button #3491
* Enhancement - Replace call to `billing-agreements/agreement-tokens` with checking the capabilities for Reference Transactions #3495
* Enhancement - Add Fastlane 3D Secure support #3493
* Enhancement - Improved PHP 8.4 compatibility #3534
* Fix - `INVALID_REQUEST` error due to wrong `landing_page` value after upgrade to 3.0.7 #3521
* Fix - Incorrect Amount via Express Payment for WooCommerce Product Bundles #3516
* Fix - Onboarding failed via "Connect to PayPal" in new UI due to race condition #3385
* Fix - Fatal error when PayPal Payments is active without WooCommerce #3502
* Fix - PayPal Subscription transaction failed in various scenarios #3515
* Fix - Rounding differences potentially lead to order failure (author @luzat) #3373
* Fix - Google Pay payment on block checkout may fail when ACDC is default payment selection #3506
* Fix - Product Prices Disappear in some cases when WooCommerce Subscriptions is active #3519
= 3.0.7 - 2025-07-01 =
* Enhancement - Remove `application_context` in favor of `experience_context` object #3431
**NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations.
**NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations.
* Enhancement - Add Contact Module feature
* Enhancement - Add WooCommerce Tracks integration
* Enhancement - Onboarding notification for Firefox browser #3433

View file

@ -401,6 +401,7 @@ export const PayPalComponent = ( {
const shouldEnableAppSwitch = () => {
// AppSwitch should only be enabled in Pay Now flows with server side shipping callback.
return (
config.scriptData.appswitch.enabled &&
! config.scriptData.final_review_enabled &&
config.scriptData.server_side_shipping_callback.enabled
);

View file

@ -27,7 +27,7 @@ export const paypalAddressToWc = ( address ) => {
admin_area_2: 'city',
postal_code: 'postcode',
};
if ( address.city ) {
if ( address?.city ) {
// address not from API, such as onShippingChange
map = {
country_code: 'country',
@ -38,7 +38,7 @@ export const paypalAddressToWc = ( address ) => {
}
const result = {};
Object.entries( map ).forEach( ( [ paypalKey, wcKey ] ) => {
if ( address[ paypalKey ] ) {
if ( address?.[ paypalKey ] ) {
result[ wcKey ] = address[ paypalKey ];
}
} );

View file

@ -252,6 +252,7 @@ class Renderer {
shouldEnableAppSwitch = () => {
// AppSwitch should only be enabled in Pay Now flows with server side shipping callback.
return (
this.defaultSettings.appswitch.enabled &&
! this.defaultSettings.final_review_enabled &&
this.defaultSettings.server_side_shipping_callback.enabled
);

View file

@ -169,6 +169,7 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'button.handle-shipping-in-paypal' ),
$container->get( 'wcgateway.server-side-shipping-callback-enabled' ),
$container->get( 'wcgateway.appswitch-enabled' ),
$container->get( 'button.helper.disabled-funding-sources' ),
$container->get( 'wcgateway.configuration.card-configuration' ),
$container->get( 'api.helper.partner-attribution' ),

View file

@ -259,6 +259,11 @@ class SmartButton implements SmartButtonInterface {
*/
private bool $server_side_shipping_callback_enabled;
/**
* Whether the AppSwitch is enabled (feature flag).
*/
private bool $appswitch_enabled;
/**
* Whether the final review is enabled in blocks settings.
*/
@ -291,6 +296,7 @@ class SmartButton implements SmartButtonInterface {
* @param LoggerInterface $logger The logger.
* @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal.
* @param bool $server_side_shipping_callback_enabled Whether the server-side shipping callback is enabled (feature flag).
* @param bool $appswitch_enabled Whether the AppSwitch is enabled (feature flag).
* @param DisabledFundingSources $disabled_funding_sources List of funding sources to be disabled.
* @param CardPaymentsConfiguration $dcc_configuration The DCC Gateway Configuration.
* @param PartnerAttribution $partner_attribution The PayPal Partner Attribution Helper.
@ -321,6 +327,7 @@ class SmartButton implements SmartButtonInterface {
LoggerInterface $logger,
bool $should_handle_shipping_in_paypal,
bool $server_side_shipping_callback_enabled,
bool $appswitch_enabled,
DisabledFundingSources $disabled_funding_sources,
CardPaymentsConfiguration $dcc_configuration,
PartnerAttribution $partner_attribution,
@ -350,6 +357,7 @@ class SmartButton implements SmartButtonInterface {
$this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal;
$this->server_side_shipping_callback_enabled = $server_side_shipping_callback_enabled;
$this->appswitch_enabled = $appswitch_enabled;
$this->disabled_funding_sources = $disabled_funding_sources;
$this->dcc_configuration = $dcc_configuration;
$this->partner_attribution = $partner_attribution;
@ -1365,6 +1373,9 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'server_side_shipping_callback' => array(
'enabled' => $this->server_side_shipping_callback_enabled,
),
'appswitch' => array(
'enabled' => $this->appswitch_enabled,
),
'needShipping' => $this->need_shipping(),
'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ),
'productType' => null,

View file

@ -120,7 +120,11 @@ class SettingsModule implements ServiceModule, ExecutableModule {
*/
add_filter(
Repository::NOTICES_FILTER,
static function ( array $notices ): array {
static function ( array $notices ) use ( $container ): array {
if ( ! $container->get( 'wcgateway.is-ppcp-settings-page' ) ) {
return $notices;
}
$message = sprintf(
// translators: %1$s is the URL for the startup guide.
__(

View file

@ -2265,4 +2265,12 @@ return array(
getenv( 'PCP_SERVER_SIDE_SHIPPING_CALLBACK_ENABLED' ) === '1'
);
},
'wcgateway.appswitch-enabled' => static function( ContainerInterface $container ) : bool {
return apply_filters(
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
'woocommerce.feature-flags.woocommerce_paypal_payments.appswitch_enabled',
getenv( 'PCP_APPSWITCH_ENABLED' ) === '1'
);
},
);

View file

@ -109,6 +109,13 @@ class ReturnUrlEndpoint {
}
}
// Replace session order for approved/completed orders.
if ( $order->status()->is( OrderStatus::APPROVED )
|| $order->status()->is( OrderStatus::COMPLETED )
) {
$this->session_handler->replace_order( $order );
}
$wc_order_id = (int) $order->purchase_units()[0]->custom_id();
if ( ! $wc_order_id ) {
// We cannot finish processing here without WC order, but at least go into the continuation mode.

View file

@ -1,6 +1,6 @@
{
"name": "woocommerce-paypal-payments",
"version": "3.0.7",
"version": "3.0.9",
"description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0",

View file

@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, credit card
Requires at least: 6.5
Tested up to: 6.8
Requires PHP: 7.4
Stable tag: 3.0.7
Stable tag: 3.0.9
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -156,9 +156,26 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
== Changelog ==
= 3.0.9 - 2025-07-31 =
* Fix - Payment via "Proceed to PayPal" may result in a redirect loop #3570
= 3.0.8 - 2025-07-28 =
* Enhancement - Migration from Legacy Settings to New Settings as opt-in via banner & button #3491
* Enhancement - Replace call to `billing-agreements/agreement-tokens` with checking the capabilities for Reference Transactions #3495
* Enhancement - Add Fastlane 3D Secure support #3493
* Enhancement - Improved PHP 8.4 compatibility #3534
* Fix - `INVALID_REQUEST` error due to wrong `landing_page` value after upgrade to 3.0.7 #3521
* Fix - Incorrect Amount via Express Payment for WooCommerce Product Bundles #3516
* Fix - Onboarding failed via "Connect to PayPal" in new UI due to race condition #3385
* Fix - Fatal error when PayPal Payments is active without WooCommerce #3502
* Fix - PayPal Subscription transaction failed in various scenarios #3515
* Fix - Rounding differences potentially lead to order failure (author @luzat) #3373
* Fix - Google Pay payment on block checkout may fail when ACDC is default payment selection #3506
* Fix - Product Prices Disappear in some cases when WooCommerce Subscriptions is active #3519
= 3.0.7 - 2025-07-01 =
* Enhancement - Remove `application_context` in favor of `experience_context` object #3431
**NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations.
**NOTE**: If you were modifying the `application_context` object programmatically, you may need to update your code to utilize `experience_context` for your customizations.
* Enhancement - Add Contact Module feature
* Enhancement - Add WooCommerce Tracks integration
* Enhancement - Onboarding notification for Firefox browser #3433

View file

@ -181,4 +181,67 @@ class CreditcardTransactionTest extends IntegrationMockedTestCase
unset($_POST['ppcp-credit-card-gateway-card-expiry']);
unset($_POST['ppcp-credit-card-gateway-card-cvc']);
}
/**
* Tests payment processing with a saved/vaulted credit card.
*
* GIVEN a WooCommerce order
* AND a saved credit card token for the customer
* AND the saved card ID in POST data
* WHEN the payment is processed through the credit card gateway
* THEN the payment should be successfully captured using the vaulted card
* AND the order status should change to processing
* AND a transaction ID should be set on the order
*/
public function testProcessPaymentVaultedCard()
{
$mockOrderEndpoint = $this->mockOrderEndpoint('CAPTURE', false, true);
$order = $this->getConfiguredOrder(
$this->customer_id,
'ppcp-credit-card-gateway',
['simple'],
[],
false
);
$paypal_order_id = 'TEST-PAYPAL-ORDER-' . uniqid();
$_POST['paypal_order_id'] = $paypal_order_id;
$order->update_meta_data('_paypal_order_id', $paypal_order_id);
$order->save();
$paymentToken = $this->setupPaymentToken($this->customer_id, 'ppcp-credit-card-gateway');
// Set the saved card in POST data (simulating frontend selection)
$_POST['saved_credit_card'] = $paymentToken->get_token();
$sessionHandler = \Mockery::mock(\WooCommerce\PayPalCommerce\Session\SessionHandler::class);
$sessionHandler->shouldReceive('order')->andReturn(null);
$sessionHandler->shouldReceive('destroy_session_data')->once();
$additionalServices = [
'session.handler' => function() use ($sessionHandler) {
return $sessionHandler;
},
'api.endpoint.payment-tokens' => function() {
return $this->mockPaymentTokensEndpoint;
}
];
$c = $this->setupTestContainer($mockOrderEndpoint, $additionalServices);
$gateway = $c->get('wcgateway.credit-card-gateway');
$result = $gateway->process_payment($order->get_id());
// Assertions
$this->assertEquals('success', $result['result']);
$this->assertArrayHasKey('redirect', $result);
// Verify order status changed
$order = wc_get_order($order->get_id()); // Refresh order
$this->assertEquals('processing', $order->get_status());
$this->assertNotEmpty($order->get_transaction_id());
// Clean up POST data
unset($_POST['saved_credit_card']);
}
}

View file

@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Tests\Integration\Transaction_tests;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Tests\Integration\IntegrationMockedTestCase;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
/**
* OXXO Gateway Integration Tests
*
* Tests OXXO payment processing functionality with minimal API mocking.
* Only third-party PayPal API calls are mocked.
*
* @group transactions
* @group skip-ci
*/
class OxxoTransactionTest extends IntegrationMockedTestCase
{
/**
* Sets up a test container with common mocks
*
* @param OrderEndpoint $orderEndpoint Mocked order endpoint
* @param array $additionalServices Additional services to override
* @return ContainerInterface
*/
protected function setupTestContainer(OrderEndpoint $orderEndpoint, array $additionalServices = []): ContainerInterface
{
$services = [
'api.endpoint.order' => function () use ($orderEndpoint) {
return $orderEndpoint;
},
];
return $this->bootstrapModule(array_merge($services, $additionalServices));
}
/**
* Data provider for different product combinations
*
* @return array
*/
public function paymentProcessingDataProvider(): array
{
// A webhook will put the pending payments to on-hold status,
// but we are not testing webhooks here.
// look at PaymentCapturePending::handle_request
return [
'simple product only' => [
'products' => ['simple'],
'expected_status' => 'pending'
],
'expensive product' => [
'products' => ['simple_expensive'],
'expected_status' => 'pending'
],
'multiple products' => [
'products' => [
['preset' => 'simple', 'quantity' => 2],
'simple_expensive'
],
'expected_status' => 'pending'
],
];
}
/**
* Tests OXXO payment processing with different product combinations.
*
* @scenario Process OXXO payment for order
* @given a WooCommerce order with various product combinations
* @and valid billing information for Mexico
* @when the OXXO payment is processed
* @then the PayPal API should be called to create and confirm payment
* @and a payer action link should be stored in order meta
* @and the order status should change to on-hold
* @and the cart should be emptied
*
* @dataProvider paymentProcessingDataProvider
*
* @param array $products Product configuration
* @param string $expected_status Expected order status after payment
*
* @return void
*/
public function testProcessPayment(array $products, string $expected_status): void
{
$mockOrderEndpoint = $this->mockOrderEndpointForOXXO();
$container = $this->setupTestContainer($mockOrderEndpoint);
$order = $this->getConfiguredOrder(
$this->customer_id,
OXXOGateway::ID,
$products,
[], // No discounts for OXXO tests
false
);
$order->set_billing_country('MX');
$order->set_billing_first_name('Juan');
$order->set_billing_last_name('Pérez');
$order->set_billing_email('juan.perez@example.com');
$order->save();
$gateway = $container->get('wcgateway.oxxo-gateway');
$result = $gateway->process_payment($order->get_id());
$this->assertEquals('success', $result['result']);
$this->assertArrayHasKey('redirect', $result);
$order = wc_get_order($order->get_id());
$this->assertEquals($expected_status, $order->get_status());
$payer_action_link = $order->get_meta('ppcp_oxxo_payer_action');
$this->assertNotEmpty($payer_action_link);
$this->assertStringContainsString('paypal.com/payment/oxxo', $payer_action_link);
}
/**
* Tests OXXO payment processing failure scenarios.
*
* @scenario Process OXXO payment with API failure
* @given a WooCommerce order with valid billing information
* @when the PayPal API fails during order creation
* @then the payment should fail gracefully
* @and the order status should be updated to failed
* @and an error notice should be displayed
*
* @return void
*/
public function testProcessPaymentFailure(): void
{
$mockOrderEndpoint = $this->mockOrderEndpointWithFailure();
$container = $this->setupTestContainer($mockOrderEndpoint);
$order = $this->getConfiguredOrder(
$this->customer_id,
OXXOGateway::ID,
['simple'],
[],
false
);
$order->set_billing_country('MX');
$order->set_billing_first_name('Juan');
$order->set_billing_last_name('Pérez');
$order->set_billing_email('juan.perez@example.com');
$order->save();
$gateway = $container->get('wcgateway.oxxo-gateway');
$result = $gateway->process_payment($order->get_id());
$this->assertEquals('failure', $result['result']);
}
/**
* Mock OrderEndpoint for successful OXXO payment processing
*
* @return OrderEndpoint
*/
private function mockOrderEndpointForOXXO(): OrderEndpoint
{
$mockEndpoint = $this->mockOrderEndpoint('CAPTURE', true, true);
// Add OXXO-specific confirm_payment_source method
$confirmResponse = (object)[
'links' => [
(object)[
'rel' => 'payer-action',
'href' => 'https://sandbox.paypal.com/payment/oxxo?token=TEST_TOKEN_123',
],
]
];
$mockEndpoint->shouldReceive('confirm_payment_source')
->andReturn($confirmResponse);
return $mockEndpoint;
}
/**
* Mock OrderEndpoint for failed payment processing
*
* @return OrderEndpoint
*/
private function mockOrderEndpointWithFailure(): OrderEndpoint
{
$mockEndpoint = $this->mockOrderEndpoint('CAPTURE', false, false);
// Override the create method to throw exception (OXXO fails at order creation)
$mockEndpoint->shouldReceive('confirm_payment_source')
->andThrow(RuntimeException::class, 'API Error');
return $mockEndpoint;
}
}

View file

@ -3,7 +3,7 @@
* Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
* Version: 3.0.7
* Version: 3.0.9
* Author: PayPal
* Author URI: https://paypal.com/
* License: GPL-2.0
@ -11,7 +11,7 @@
* Requires Plugins: woocommerce
* Requires at least: 6.5
* WC requires at least: 9.6
* WC tested up to: 9.9
* WC tested up to: 10.0
* Text Domain: woocommerce-paypal-payments
*
* @package WooCommerce\PayPalCommerce
@ -27,7 +27,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' );
define( 'PAYPAL_URL', 'https://www.paypal.com' );
define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' );
define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' );
define( 'PAYPAL_INTEGRATION_DATE', '2025-06-25' );
define( 'PAYPAL_INTEGRATION_DATE', '2025-07-31' );
define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' );
! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' );