Merge branch 'trunk' into PCP-2722-add-block-checkout-compatibility-to-advanced-card-processing

This commit is contained in:
Emili Castells Guasch 2024-05-17 17:21:12 +02:00
commit 4b3cd53f73
21 changed files with 665 additions and 51 deletions

View file

@ -6,6 +6,8 @@
* Fix - Allow PUI Gateway for refund processor #2192 * Fix - Allow PUI Gateway for refund processor #2192
* Fix - Notice on newly created block cart checkout #2211 * Fix - Notice on newly created block cart checkout #2211
* Fix - Apple Pay button in the editor #2177 * Fix - Apple Pay button in the editor #2177
* Fix - Allow shipping callback and skipping confirmation page from any express button #2236
* Enhancement - Use admin theme color #1602
= 2.7.0 - 2024-04-30 = = 2.7.0 - 2024-04-30 =
* Fix - Zero sum subscriptions cause CANNOT_BE_ZERO_OR_NEGATIVE when using Vault v3 #2152 * Fix - Zero sum subscriptions cause CANNOT_BE_ZERO_OR_NEGATIVE when using Vault v3 #2152

View file

@ -37,6 +37,31 @@ return array(
return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) ); return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
}; };
$label = __(
'Enable this option to require customers to manually confirm express payments on the checkout page.
<p class="description">This ensures they can review the order, update shipping options, and fill in eventual custom fields necessary for the transaction.</p>
<p class="description">If this is disabled, the system will automatically synchronize shipping options with PayPal and bypass the final checkout confirmation. This expedites the checkout process but prevents buyers from filling in eventual custom fields and reviewing final details before finalizing the payment.</p>',
'woocommerce-paypal-payments'
);
if ( wc_terms_and_conditions_page_id() > 0 ) {
$label .= __(
'<div class="ppcp-notice ppcp-notice-warning"><p><span class="highlight">Important:</span> Your store has a <a href="/wp-admin/admin.php?page=wc-settings&tab=advanced" target="_blank">Terms and Conditions</a> page configured. Buyers who use a PayPal express payment method will not be able to consent to the terms on the <code>Classic Checkout</code>, as the final checkout confirmation will be skipped.</p></div>',
'woocommerce-paypal-payments'
);
}
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
if ( $subscription_helper->plugin_is_active() ) {
$label .= __(
'<div class="ppcp-notice ppcp-notice-warning"><p><span class="highlight">Important:</span> Cannot be deactivated while the WooCommerce Subscriptions plugin is active.</p></div>',
'woocommerce-paypal-payments'
);
}
$should_disable_checkbox = $subscription_helper->plugin_is_active() || apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false );
return $insert_after( return $insert_after(
$fields, $fields,
'smart_button_locations', 'smart_button_locations',
@ -44,16 +69,13 @@ return array(
'blocks_final_review_enabled' => array( 'blocks_final_review_enabled' => array(
'title' => __( 'Require final confirmation on checkout', 'woocommerce-paypal-payments' ), 'title' => __( 'Require final confirmation on checkout', 'woocommerce-paypal-payments' ),
'type' => 'checkbox', 'type' => 'checkbox',
'label' => __( 'label' => $label,
'Require customers to confirm express payments from the Cart and Express Checkout on the checkout page. 'default' => false,
<p class="description">If this setting is not enabled, <a href="https://woocommerce.com/document/woocommerce-paypal-payments/#blocks-faq" target="_blank">payment confirmation on the checkout will be skipped</a>.
Skipping the final confirmation on the checkout page may impact the buyer experience during the PayPal payment process.</p>',
'woocommerce-paypal-payments'
),
'default' => true,
'screens' => array( State::STATE_START, State::STATE_ONBOARDED ), 'screens' => array( State::STATE_START, State::STATE_ONBOARDED ),
'requirements' => array(), 'requirements' => array(),
'gateway' => 'paypal', 'gateway' => 'paypal',
'class' => array( 'ppcp-grayed-out-text' ),
'input_class' => $should_disable_checkbox ? array( 'ppcp-disabled-checkbox' ) : array(),
), ),
) )
); );

View file

@ -139,7 +139,17 @@ export const paypalOrderToWcAddresses = (order) => {
billingAddress = paypalPayerToWc(order.payer); billingAddress = paypalPayerToWc(order.payer);
// no billing address, such as if billing address retrieval is not allowed in the merchant account // no billing address, such as if billing address retrieval is not allowed in the merchant account
if (!billingAddress.address_line_1) { if (!billingAddress.address_line_1) {
billingAddress = {...shippingAddress, ...paypalPayerToWc(order.payer)}; // use only non empty values from payer address, otherwise it will override shipping address
let payerAddress = Object.fromEntries(
Object.entries(billingAddress).filter(
([key, value]) => value !== '' && key !== 'country'
)
);
billingAddress = {
...shippingAddress,
...payerAddress
};
} }
} }

View file

@ -82,7 +82,7 @@ const PayPalComponent = ({
window.ppcpContinuationFilled = true; window.ppcpContinuationFilled = true;
}, []) }, [])
const createOrder = async () => { const createOrder = async (data, actions) => {
try { try {
const res = await fetch(config.scriptData.ajax.create_order.endpoint, { const res = await fetch(config.scriptData.ajax.create_order.endpoint, {
method: 'POST', method: 'POST',
@ -93,7 +93,8 @@ const PayPalComponent = ({
context: config.scriptData.context, context: config.scriptData.context,
payment_method: 'ppcp-gateway', payment_method: 'ppcp-gateway',
funding_source: window.ppcpFundingSource ?? 'paypal', funding_source: window.ppcpFundingSource ?? 'paypal',
createaccount: false createaccount: false,
payment_source: data.paymentSource
}), }),
}); });
@ -296,10 +297,15 @@ const PayPalComponent = ({
onClick(); onClick();
}; };
const isVenmoAndVaultingEnabled = () => {
return window.ppcpFundingSource === 'venmo' && config.scriptData.vaultingEnabled;
}
let handleShippingOptionsChange = null; let handleShippingOptionsChange = null;
let handleShippingAddressChange = null; let handleShippingAddressChange = null;
let handleSubscriptionShippingOptionsChange = null; let handleSubscriptionShippingOptionsChange = null;
let handleSubscriptionShippingAddressChange = null; let handleSubscriptionShippingAddressChange = null;
if (shippingData.needsShipping && !config.finalReviewEnabled) { if (shippingData.needsShipping && !config.finalReviewEnabled) {
handleShippingOptionsChange = async (data, actions) => { handleShippingOptionsChange = async (data, actions) => {
try { try {

View file

@ -60,7 +60,8 @@ class CartActionHandler {
funding_source: window.ppcpFundingSource, funding_source: window.ppcpFundingSource,
bn_code:bnCode, bn_code:bnCode,
payer, payer,
context:this.config.context context:this.config.context,
payment_source: data.paymentSource
}), }),
}).then(function(res) { }).then(function(res) {
return res.json(); return res.json();

View file

@ -0,0 +1,126 @@
import {paypalAddressToWc} from "../../../../../ppcp-blocks/resources/js/Helper/Address.js";
import {convertKeysToSnakeCase} from "../../../../../ppcp-blocks/resources/js/Helper/Helper.js";
/**
* Handles the shipping option change in PayPal.
*
* @param data
* @param actions
* @param config
* @returns {Promise<void>}
*/
export const handleShippingOptionsChange = async (data, actions, config) => {
try {
const shippingOptionId = data.selectedShippingOption?.id;
if (shippingOptionId) {
await fetch(config.ajax.update_customer_shipping.shipping_options.endpoint, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce,
},
body: JSON.stringify({
rate_id: shippingOptionId,
})
})
.then(response => {
return response.json();
})
.then(cardData => {
const shippingMethods = document.querySelectorAll('.shipping_method');
shippingMethods.forEach(function(method) {
if (method.value === shippingOptionId) {
method.checked = true;
}
});
})
}
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
}
} catch (e) {
console.error(e);
actions.reject();
}
};
/**
* Handles the shipping address change in PayPal.
*
* @param data
* @param actions
* @param config
* @returns {Promise<void>}
*/
export const handleShippingAddressChange = async (data, actions, config) => {
try {
const address = paypalAddressToWc(convertKeysToSnakeCase(data.shippingAddress));
// Retrieve current cart contents
await fetch(config.ajax.update_customer_shipping.shipping_address.cart_endpoint)
.then(response => {
return response.json();
})
.then(cartData => {
// Update shipping address in the cart data
cartData.shipping_address.address_1 = address.address_1;
cartData.shipping_address.address_2 = address.address_2;
cartData.shipping_address.city = address.city;
cartData.shipping_address.state = address.state;
cartData.shipping_address.postcode = address.postcode;
cartData.shipping_address.country = address.country;
// Send update request
return fetch(config.ajax.update_customer_shipping.shipping_address.update_customer_endpoint, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-WC-Store-API-Nonce': config.ajax.update_customer_shipping.wp_rest_nonce,
},
body: JSON.stringify({
shipping_address: cartData.shipping_address,
})
}).then(function (res) {
return res.json();
}).then(function (customerData) {
jQuery(".cart_totals .shop_table").load(location.href + " " + ".cart_totals .shop_table" + ">*", "");
})
})
const res = await fetch(config.ajax.update_shipping.endpoint, {
method: 'POST',
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.ajax.update_shipping.nonce,
order_id: data.orderID,
})
});
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
}
} catch (e) {
console.error(e);
actions.reject();
}
};

View file

@ -10,6 +10,7 @@ const onApprove = (context, errorHandler) => {
nonce: context.config.ajax.approve_order.nonce, nonce: context.config.ajax.approve_order.nonce,
order_id:data.orderID, order_id:data.orderID,
funding_source: window.ppcpFundingSource, funding_source: window.ppcpFundingSource,
should_create_wc_order: !context.config.vaultingEnabled || data.paymentSource !== 'venmo'
}) })
}).then((res)=>{ }).then((res)=>{
return res.json(); return res.json();
@ -20,7 +21,11 @@ const onApprove = (context, errorHandler) => {
errorHandler.genericError(); errorHandler.genericError();
}); });
} }
location.href = context.config.redirect;
let orderReceivedUrl = data.data?.order_received_url
location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect;
}); });
} }

View file

@ -3,6 +3,10 @@ import {loadScript} from "@paypal/paypal-js";
import {keysToCamelCase} from "../Helper/Utils"; import {keysToCamelCase} from "../Helper/Utils";
import widgetBuilder from "./WidgetBuilder"; import widgetBuilder from "./WidgetBuilder";
import {normalizeStyleForFundingSource} from "../Helper/Style"; import {normalizeStyleForFundingSource} from "../Helper/Style";
import {
handleShippingOptionsChange,
handleShippingAddressChange,
} from "../Helper/ShippingHandler.js";
class Renderer { class Renderer {
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) { constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
@ -64,6 +68,15 @@ class Renderer {
} }
} }
shouldHandleShippingInPaypal = (venmoButtonClicked) => {
if (!this.defaultSettings.should_handle_shipping_in_paypal) {
console.log('no')
return false;
}
return !venmoButtonClicked || !this.defaultSettings.vaultingEnabled;
}
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) { renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) { if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) {
// Try to render registered buttons again in case they were removed from the DOM by an external source. // Try to render registered buttons again in case they were removed from the DOM by an external source.
@ -75,17 +88,24 @@ class Renderer {
contextConfig.fundingSource = fundingSource; contextConfig.fundingSource = fundingSource;
} }
let venmoButtonClicked = false;
const buttonsOptions = () => { const buttonsOptions = () => {
return { return {
style, style,
...contextConfig, ...contextConfig,
onClick: this.onSmartButtonClick, onClick: (data, actions) => {
venmoButtonClicked = data.fundingSource === 'venmo'
this.onSmartButtonClick
},
onInit: (data, actions) => { onInit: (data, actions) => {
if (this.onSmartButtonsInit) { if (this.onSmartButtonsInit) {
this.onSmartButtonsInit(data, actions); this.onSmartButtonsInit(data, actions);
} }
this.handleOnButtonsInit(wrapper, data, actions); this.handleOnButtonsInit(wrapper, data, actions);
}, },
onShippingOptionsChange: (data, actions) => this.shouldHandleShippingInPaypal(venmoButtonClicked) ? handleShippingOptionsChange(data, actions, this.defaultSettings) : null,
onShippingAddressChange: (data, actions) => this.shouldHandleShippingInPaypal(venmoButtonClicked) ? handleShippingAddressChange(data, actions, this.defaultSettings) : null,
} }
} }

View file

@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver; use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\WooCommerceOrderCreator;
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator; use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
@ -147,7 +148,8 @@ return array(
$container->get( 'wcgateway.funding-sources-without-redirect' ), $container->get( 'wcgateway.funding-sources-without-redirect' ),
$container->get( 'vaulting.vault-v3-enabled' ), $container->get( 'vaulting.vault-v3-enabled' ),
$container->get( 'api.endpoint.payment-tokens' ), $container->get( 'api.endpoint.payment-tokens' ),
$container->get( 'woocommerce.logger.woocommerce' ) $container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'button.handle-shipping-in-paypal' )
); );
}, },
'button.url' => static function ( ContainerInterface $container ): string { 'button.url' => static function ( ContainerInterface $container ): string {
@ -157,7 +159,13 @@ return array(
); );
}, },
'button.pay-now-contexts' => static function ( ContainerInterface $container ): array { 'button.pay-now-contexts' => static function ( ContainerInterface $container ): array {
return array( 'checkout', 'pay-now' ); $defaults = array( 'checkout', 'pay-now' );
if ( $container->get( 'button.handle-shipping-in-paypal' ) ) {
return array_merge( $defaults, array( 'cart', 'product', 'mini-cart' ) );
}
return $defaults;
}, },
'button.request-data' => static function ( ContainerInterface $container ): RequestData { 'button.request-data' => static function ( ContainerInterface $container ): RequestData {
return new RequestData(); return new RequestData();
@ -221,14 +229,18 @@ return array(
return new EarlyOrderHandler( $state, $order_processor, $session_handler ); return new EarlyOrderHandler( $state, $order_processor, $session_handler );
}, },
'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint { 'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint {
$request_data = $container->get( 'button.request-data' ); $request_data = $container->get( 'button.request-data' );
$order_endpoint = $container->get( 'api.endpoint.order' ); $order_endpoint = $container->get( 'api.endpoint.order' );
$session_handler = $container->get( 'session.handler' ); $session_handler = $container->get( 'session.handler' );
$three_d_secure = $container->get( 'button.helper.three-d-secure' ); $three_d_secure = $container->get( 'button.helper.three-d-secure' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$dcc_applies = $container->get( 'api.helpers.dccapplies' ); $dcc_applies = $container->get( 'api.helpers.dccapplies' );
$order_helper = $container->get( 'api.order-helper' ); $order_helper = $container->get( 'api.order-helper' );
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $final_review_enabled = $container->get( 'blocks.settings.final_review_enabled' );
$wc_order_creator = $container->get( 'button.helper.wc-order-creator' );
$gateway = $container->get( 'wcgateway.paypal-gateway' );
$subscription_helper = $container->get( 'wc-subscriptions.helper' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new ApproveOrderEndpoint( return new ApproveOrderEndpoint(
$request_data, $request_data,
$order_endpoint, $order_endpoint,
@ -237,6 +249,10 @@ return array(
$settings, $settings,
$dcc_applies, $dcc_applies,
$order_helper, $order_helper,
$final_review_enabled,
$gateway,
$wc_order_creator,
$subscription_helper,
$logger $logger
); );
}, },
@ -342,6 +358,10 @@ return array(
* May result in slower popup performance, additional loading. * May result in slower popup performance, additional loading.
*/ */
'button.handle-shipping-in-paypal' => static function ( ContainerInterface $container ): bool { 'button.handle-shipping-in-paypal' => static function ( ContainerInterface $container ): bool {
return false; return ! $container->get( 'blocks.settings.final_review_enabled' );
},
'button.helper.wc-order-creator' => static function ( ContainerInterface $container ): WooCommerceOrderCreator {
return new WooCommerceOrderCreator( $container->get( 'wcgateway.funding-source.renderer' ), $container->get( 'session.handler' ) );
}, },
); );

View file

@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
@ -217,6 +218,13 @@ class SmartButton implements SmartButtonInterface {
*/ */
private $logger; private $logger;
/**
* Whether the shipping should be handled in PayPal.
*
* @var bool
*/
private $should_handle_shipping_in_paypal;
/** /**
* SmartButton constructor. * SmartButton constructor.
* *
@ -242,6 +250,7 @@ class SmartButton implements SmartButtonInterface {
* @param bool $vault_v3_enabled Whether Vault v3 module is enabled. * @param bool $vault_v3_enabled Whether Vault v3 module is enabled.
* @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint. * @param PaymentTokensEndpoint $payment_tokens_endpoint Payment tokens endpoint.
* @param LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
* @param bool $should_handle_shipping_in_paypal Whether the shipping should be handled in PayPal.
*/ */
public function __construct( public function __construct(
string $module_url, string $module_url,
@ -265,7 +274,8 @@ class SmartButton implements SmartButtonInterface {
array $funding_sources_without_redirect, array $funding_sources_without_redirect,
bool $vault_v3_enabled, bool $vault_v3_enabled,
PaymentTokensEndpoint $payment_tokens_endpoint, PaymentTokensEndpoint $payment_tokens_endpoint,
LoggerInterface $logger LoggerInterface $logger,
bool $should_handle_shipping_in_paypal
) { ) {
$this->module_url = $module_url; $this->module_url = $module_url;
@ -290,6 +300,7 @@ class SmartButton implements SmartButtonInterface {
$this->vault_v3_enabled = $vault_v3_enabled; $this->vault_v3_enabled = $vault_v3_enabled;
$this->logger = $logger; $this->logger = $logger;
$this->payment_tokens_endpoint = $payment_tokens_endpoint; $this->payment_tokens_endpoint = $payment_tokens_endpoint;
$this->should_handle_shipping_in_paypal = $should_handle_shipping_in_paypal;
} }
/** /**
@ -1133,6 +1144,21 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentTokenForGuest::ENDPOINT ), 'endpoint' => \WC_AJAX::get_endpoint( CreatePaymentTokenForGuest::ENDPOINT ),
'nonce' => wp_create_nonce( CreatePaymentTokenForGuest::nonce() ), 'nonce' => wp_create_nonce( CreatePaymentTokenForGuest::nonce() ),
), ),
'update_shipping' => array(
'endpoint' => \WC_AJAX::get_endpoint( UpdateShippingEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( UpdateShippingEndpoint::nonce() ),
),
'update_customer_shipping' => array(
'shipping_options' => array(
'endpoint' => '/wp-json/wc/store/cart/select-shipping-rate',
),
'shipping_address' => array(
'cart_endpoint' => '/wp-json/wc/store/cart/',
'update_customer_endpoint' => '/wp-json/wc/store/v1/cart/update-customer/',
),
'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ),
'update_shipping_method' => \WC_AJAX::get_endpoint( 'update_shipping_method' ),
),
), ),
'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(), 'cart_contains_subscription' => $this->subscription_helper->cart_contains_subscription(),
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(), 'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
@ -1253,6 +1279,8 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'user' => array( 'user' => array(
'is_logged' => is_user_logged_in(), 'is_logged' => is_user_logged_in(),
), ),
'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(),
'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ),
); );
if ( 'pay-now' === $this->context() ) { if ( 'pay-now' === $this->context() ) {
@ -1333,7 +1361,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages
'integration-date' => PAYPAL_INTEGRATION_DATE, 'integration-date' => PAYPAL_INTEGRATION_DATE,
'components' => implode( ',', $this->components() ), 'components' => implode( ',', $this->components() ),
'vault' => ( $this->can_save_vault_token() || $this->subscription_helper->need_subscription_intent( $subscription_mode ) ) ? 'true' : 'false', 'vault' => ( $this->can_save_vault_token() || $this->subscription_helper->need_subscription_intent( $subscription_mode ) ) ? 'true' : 'false',
'commit' => in_array( $context, $this->pay_now_contexts, true ) ? 'true' : 'false', 'commit' => 'false',
'intent' => $intent, 'intent' => $intent,
); );

View file

@ -14,20 +14,25 @@ use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentSource;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Button\Helper\WooCommerceOrderCreator;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
/** /**
* Class ApproveOrderEndpoint * Class ApproveOrderEndpoint
*/ */
class ApproveOrderEndpoint implements EndpointInterface { class ApproveOrderEndpoint implements EndpointInterface {
use ContextTrait;
const ENDPOINT = 'ppc-approve-order'; const ENDPOINT = 'ppc-approve-order';
/** /**
@ -79,6 +84,34 @@ class ApproveOrderEndpoint implements EndpointInterface {
*/ */
protected $order_helper; protected $order_helper;
/**
* Whether the final review is enabled.
*
* @var bool
*/
protected $final_review_enabled;
/**
* The WC gateway.
*
* @var PayPalGateway
*/
protected $gateway;
/**
* The WooCommerce order creator.
*
* @var WooCommerceOrderCreator
*/
protected $wc_order_creator;
/**
* The Subscription Helper.
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/** /**
* The logger. * The logger.
* *
@ -89,14 +122,18 @@ class ApproveOrderEndpoint implements EndpointInterface {
/** /**
* ApproveOrderEndpoint constructor. * ApproveOrderEndpoint constructor.
* *
* @param RequestData $request_data The request data helper. * @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint. * @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler. * @param SessionHandler $session_handler The session handler.
* @param ThreeDSecure $three_d_secure The 3d secure helper object. * @param ThreeDSecure $three_d_secure The 3d secure helper object.
* @param Settings $settings The settings. * @param Settings $settings The settings.
* @param DccApplies $dcc_applies The DCC applies object. * @param DccApplies $dcc_applies The DCC applies object.
* @param OrderHelper $order_helper The order helper. * @param OrderHelper $order_helper The order helper.
* @param LoggerInterface $logger The logger. * @param bool $final_review_enabled Whether the final review is enabled.
* @param PayPalGateway $gateway The WC gateway.
* @param WooCommerceOrderCreator $wc_order_creator The WooCommerce order creator.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
RequestData $request_data, RequestData $request_data,
@ -106,17 +143,25 @@ class ApproveOrderEndpoint implements EndpointInterface {
Settings $settings, Settings $settings,
DccApplies $dcc_applies, DccApplies $dcc_applies,
OrderHelper $order_helper, OrderHelper $order_helper,
bool $final_review_enabled,
PayPalGateway $gateway,
WooCommerceOrderCreator $wc_order_creator,
SubscriptionHelper $subscription_helper,
LoggerInterface $logger LoggerInterface $logger
) { ) {
$this->request_data = $request_data; $this->request_data = $request_data;
$this->api_endpoint = $order_endpoint; $this->api_endpoint = $order_endpoint;
$this->session_handler = $session_handler; $this->session_handler = $session_handler;
$this->threed_secure = $three_d_secure; $this->threed_secure = $three_d_secure;
$this->settings = $settings; $this->settings = $settings;
$this->dcc_applies = $dcc_applies; $this->dcc_applies = $dcc_applies;
$this->order_helper = $order_helper; $this->order_helper = $order_helper;
$this->logger = $logger; $this->final_review_enabled = $final_review_enabled;
$this->gateway = $gateway;
$this->wc_order_creator = $wc_order_creator;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger;
} }
/** /**
@ -182,6 +227,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
); );
} }
$this->session_handler->replace_order( $order ); $this->session_handler->replace_order( $order );
wp_send_json_success(); wp_send_json_success();
} }
@ -200,6 +246,19 @@ class ApproveOrderEndpoint implements EndpointInterface {
$this->session_handler->replace_funding_source( $funding_source ); $this->session_handler->replace_funding_source( $funding_source );
$this->session_handler->replace_order( $order ); $this->session_handler->replace_order( $order );
if ( ! $this->subscription_helper->plugin_is_active() && apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false ) ) {
$this->toggle_final_review_enabled_setting();
}
$should_create_wc_order = $data['should_create_wc_order'] ?? false;
if ( ! $this->final_review_enabled && ! $this->is_checkout() && $should_create_wc_order ) {
$wc_order = $this->wc_order_creator->create_from_paypal_order( $order, WC()->cart );
$this->gateway->process_payment( $wc_order->get_id() );
$order_received_url = $wc_order->get_checkout_order_received_url();
wp_send_json_success( array( 'order_received_url' => $order_received_url ) );
}
wp_send_json_success(); wp_send_json_success();
return true; return true;
} catch ( Exception $error ) { } catch ( Exception $error ) {
@ -216,4 +275,15 @@ class ApproveOrderEndpoint implements EndpointInterface {
return false; return false;
} }
} }
/**
* Will toggle the "final confirmation" checkbox.
*
* @return void
*/
protected function toggle_final_review_enabled_setting(): void {
$final_review_enabled_setting = $this->settings->has( 'blocks_final_review_enabled' ) && $this->settings->get( 'blocks_final_review_enabled' );
$final_review_enabled_setting ? $this->settings->set( 'blocks_final_review_enabled', false ) : $this->settings->set( 'blocks_final_review_enabled', true );
$this->settings->persist();
}
} }

View file

@ -246,6 +246,7 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->parsed_request_data = $data; $this->parsed_request_data = $data;
$payment_method = $data['payment_method'] ?? ''; $payment_method = $data['payment_method'] ?? '';
$funding_source = $data['funding_source'] ?? ''; $funding_source = $data['funding_source'] ?? '';
$payment_source = $data['payment_source'] ?? '';
$wc_order = null; $wc_order = null;
if ( 'pay-now' === $data['context'] ) { if ( 'pay-now' === $data['context'] ) {
$wc_order = wc_get_order( (int) $data['order_id'] ); $wc_order = wc_get_order( (int) $data['order_id'] );
@ -261,7 +262,7 @@ class CreateOrderEndpoint implements EndpointInterface {
} }
$this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order ); $this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
} else { } else {
$this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->handle_shipping_in_paypal ); $this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->should_handle_shipping_in_paypal( $payment_source ) );
// Do not allow completion by webhooks when started via non-checkout buttons, // Do not allow completion by webhooks when started via non-checkout buttons,
// it is needed only for some APMs in checkout. // it is needed only for some APMs in checkout.
@ -610,4 +611,20 @@ class CreateOrderEndpoint implements EndpointInterface {
'custom_id' => $order->purchase_units()[0]->custom_id(), 'custom_id' => $order->purchase_units()[0]->custom_id(),
); );
} }
/**
* Checks if the shipping should be handled in PayPal popup.
*
* @param string $payment_source The payment source.
* @return bool true if the shipping should be handled in PayPal popup, otherwise false.
*/
protected function should_handle_shipping_in_paypal( string $payment_source ): bool {
$is_vaulting_enabled = $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' );
if ( ! $this->handle_shipping_in_paypal ) {
return false;
}
return ! $is_vaulting_enabled || $payment_source !== 'venmo';
}
} }

View file

@ -0,0 +1,228 @@
<?php
/**
* Can create WC orders.
*
* @package WooCommerce\PayPalCommerce\Button\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button\Helper;
use RuntimeException;
use WC_Cart;
use WC_Order;
use WC_Order_Item_Product;
use WC_Order_Item_Shipping;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class WooCommerceOrderCreator
*/
class WooCommerceOrderCreator {
/**
* The funding source renderer.
*
* @var FundingSourceRenderer
*/
protected $funding_source_renderer;
/**
* The Session handler.
*
* @var SessionHandler
*/
protected $session_handler;
/**
* WooCommerceOrderCreator constructor.
*
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param SessionHandler $session_handler The session handler.
*/
public function __construct(
FundingSourceRenderer $funding_source_renderer,
SessionHandler $session_handler
) {
$this->funding_source_renderer = $funding_source_renderer;
$this->session_handler = $session_handler;
}
/**
* Creates WC order based on given PayPal order.
*
* @param Order $order The PayPal order.
* @param WC_Cart $wc_cart The Cart.
* @return WC_Order The WC order.
* @throws RuntimeException If problem creating.
*/
public function create_from_paypal_order( Order $order, WC_Cart $wc_cart ): WC_Order {
$wc_order = wc_create_order();
if ( ! $wc_order instanceof WC_Order ) {
throw new RuntimeException( 'Problem creating WC order.' );
}
$this->configure_line_items( $wc_order, $wc_cart );
$this->configure_shipping( $wc_order, $order->payer(), $order->purchase_units()[0]->shipping() );
$this->configure_payment_source( $wc_order );
$this->configure_customer( $wc_order );
$this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() );
$wc_order->calculate_totals();
$wc_order->save();
return $wc_order;
}
/**
* Configures the line items.
*
* @param WC_Order $wc_order The WC order.
* @param WC_Cart $wc_cart The Cart.
* @return void
*/
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart ): void {
$cart_contents = $wc_cart->get_cart();
foreach ( $cart_contents as $cart_item ) {
$product_id = $cart_item['product_id'] ?? 0;
$variation_id = $cart_item['variation_id'] ?? 0;
$quantity = $cart_item['quantity'] ?? 0;
$variation_attributes = $cart_item['variation'];
$item = new WC_Order_Item_Product();
$item->set_product_id( $product_id );
$item->set_quantity( $quantity );
if ( $variation_id ) {
$item->set_variation_id( $variation_id );
$item->set_variation( $variation_attributes );
}
$product = wc_get_product( $variation_id ?: $product_id );
if ( ! $product ) {
return;
}
$item->set_name( $product->get_name() );
$item->set_subtotal( $product->get_price() * $quantity );
$item->set_total( $product->get_price() * $quantity );
$wc_order->add_item( $item );
}
}
/**
* Configures the shipping & billing addresses for WC order from given payer.
*
* @param WC_Order $wc_order The WC order.
* @param Payer|null $payer The payer.
* @param Shipping|null $shipping The shipping.
* @return void
*/
protected function configure_shipping( WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping ): void {
$shipping_address = null;
$billing_address = null;
$shipping_options = null;
if ( $payer ) {
$address = $payer->address();
$payer_name = $payer->name();
$billing_address = array(
'first_name' => $payer_name ? $payer_name->given_name() : '',
'last_name' => $payer_name ? $payer_name->surname() : '',
'address_1' => $address ? $address->address_line_1() : '',
'address_2' => $address ? $address->address_line_2() : '',
'city' => $address ? $address->admin_area_2() : '',
'state' => $address ? $address->admin_area_1() : '',
'postcode' => $address ? $address->postal_code() : '',
'country' => $address ? $address->country_code() : '',
);
}
if ( $shipping ) {
$address = $shipping->address();
$shipping_address = array(
'first_name' => $shipping->name(),
'last_name' => '',
'address_1' => $address->address_line_1(),
'address_2' => $address->address_line_2(),
'city' => $address->admin_area_2(),
'state' => $address->admin_area_1(),
'postcode' => $address->postal_code(),
'country' => $address->country_code(),
);
$shipping_options = $shipping->options()[0] ?? '';
}
if ( $shipping_address ) {
$wc_order->set_shipping_address( $shipping_address );
}
if ( $billing_address || $shipping_address ) {
$wc_order->set_billing_address( $billing_address ?: $shipping_address );
}
if ( $shipping_options ) {
$shipping = new WC_Order_Item_Shipping();
$shipping->set_method_title( $shipping_options->label() );
$shipping->set_method_id( $shipping_options->id() );
$shipping->set_total( $shipping_options->amount()->value_str() );
$wc_order->add_item( $shipping );
}
}
/**
* Configures the payment source.
*
* @param WC_Order $wc_order The WC order.
* @return void
*/
protected function configure_payment_source( WC_Order $wc_order ): void {
$funding_source = $this->session_handler->funding_source();
$wc_order->set_payment_method( PayPalGateway::ID );
if ( $funding_source ) {
$wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) );
}
}
/**
* Configures the customer ID.
*
* @param WC_Order $wc_order The WC order.
* @return void
*/
protected function configure_customer( WC_Order $wc_order ): void {
$current_user = wp_get_current_user();
if ( $current_user->ID !== 0 ) {
$wc_order->set_customer_id( $current_user->ID );
}
}
/**
* Configures the applied coupons.
*
* @param WC_Order $wc_order The WC order.
* @param string[] $coupons The list of applied coupons.
* @return void
*/
protected function configure_coupons( WC_Order $wc_order, array $coupons ): void {
foreach ( $coupons as $coupon_code ) {
$wc_order->apply_coupon( $coupon_code );
}
}
}

View file

@ -48,6 +48,45 @@
margin-right: 16px; margin-right: 16px;
border-top-color: #B1B7BD; border-top-color: #B1B7BD;
} }
.css-168cq4n-checkbox_input:checked + label > span > span:first-of-type {
background-color: var(--wp-admin-theme-color);
border-color: var(--wp-admin-theme-color);
}
.css-168cq4n-checkbox_input:focus + label > span > span:first-of-type {
outline: var(--wp-admin-theme-color);
}
span[class*="-svg-size_sm-SelectedMain-selected_icon"],
span[class*="-svg-size_xs-selected_icon"] {
color: var(--wp-admin-theme-color);
}
button[class*="-dropdown_menu_button-text_field_value_sm-active-active"] {
outline: var(--wp-admin-theme-color) solid 0.125rem;
}
button[class*="-dropdown_menu_button-text_field_value_sm-active"] {
&:focus {
outline: var(--wp-admin-theme-color) solid 0.125rem;
}
}
.css-gzgyp8-control:checked ~ label {
background-color: var(--wp-admin-theme-color);
border: var(--wp-admin-theme-color);
outline: var(--wp-admin-theme-color) solid 0.125rem;
}
.css-gzgyp8-control:focus ~ label {
outline: var(--wp-admin-theme-color) solid 0.125rem;
}
.css-gzgyp8-control:checked ~ span {
border: 0.0625rem solid var(--wp-admin-theme-color);
}
} }
#field-pay_later_messaging_heading h3{ #field-pay_later_messaging_heading h3{

View file

@ -61,8 +61,8 @@ $background-ident-color: #fbfbfb;
border: 1px solid #c3c4c7; border: 1px solid #c3c4c7;
border-left-width: 4px; border-left-width: 4px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
margin: 5px 15px 2px; margin: 5px 0px 2px;
padding: 1px 12px; padding: 0px 12px 4px 12px;
} }
.ppcp-notice-warning { .ppcp-notice-warning {

View file

@ -347,6 +347,7 @@ return array(
$container->get( 'api.partner_merchant_id-production' ), $container->get( 'api.partner_merchant_id-production' ),
$container->get( 'api.partner_merchant_id-sandbox' ), $container->get( 'api.partner_merchant_id-sandbox' ),
$container->get( 'api.endpoint.billing-agreements' ), $container->get( 'api.endpoint.billing-agreements' ),
$container->get( 'wc-subscriptions.helper' ),
$logger $logger
); );
}, },
@ -1461,7 +1462,7 @@ return array(
$button_text = $enabled $button_text = $enabled
? esc_html__( 'Settings', 'woocommerce-paypal-payments' ) ? esc_html__( 'Settings', 'woocommerce-paypal-payments' )
: esc_html__( 'Enable Advanced PayPal Wallet', 'woocommerce-paypal-payments' ); : esc_html__( 'Enable saving PayPal & Venmo', 'woocommerce-paypal-payments' );
$enable_url = $environment->current_environment_is( Environment::PRODUCTION ) $enable_url = $environment->current_environment_is( Environment::PRODUCTION )
? $container->get( 'wcgateway.enable-reference-transactions-url-live' ) ? $container->get( 'wcgateway.enable-reference-transactions-url-live' )

View file

@ -263,7 +263,7 @@ class SettingsPageAssets {
'reference_transaction_enabled' => $this->billing_agreements_endpoint->reference_transaction_enabled(), 'reference_transaction_enabled' => $this->billing_agreements_endpoint->reference_transaction_enabled(),
'vaulting_must_enable_advanced_wallet_message' => sprintf( 'vaulting_must_enable_advanced_wallet_message' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML <a> tag. // translators: %1$s and %2$s are the opening and closing of HTML <a> tag.
esc_html__( 'Your PayPal account must be enabled for the %1$sAdvanced PayPal Wallet%2$s to use PayPal Vaulting.', 'woocommerce-paypal-payments' ), esc_html__( 'Your PayPal account must be eligible to %1$ssave PayPal and Venmo payment methods%2$s to enable PayPal Vaulting.', 'woocommerce-paypal-payments' ),
'<a href="/wp-admin/admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading">', '<a href="/wp-admin/admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading">',
'</a>' '</a>'
), ),

View file

@ -411,7 +411,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'gateway' => Settings::CONNECTION_TAB_ID, 'gateway' => Settings::CONNECTION_TAB_ID,
), ),
'ppcp_reference_transactions_status' => array( 'ppcp_reference_transactions_status' => array(
'title' => __( 'Advanced PayPal Wallet', 'woocommerce-paypal-payments' ), 'title' => __( 'Save PayPal & Venmo payment methods', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-text', 'type' => 'ppcp-text',
'text' => $container->get( 'wcgateway.settings.connection.reference-transactions-status-text' ), 'text' => $container->get( 'wcgateway.settings.connection.reference-transactions-status-text' ),
'screens' => array( 'screens' => array(

View file

@ -23,6 +23,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar; use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger; use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
@ -160,6 +161,13 @@ class SettingsListener {
*/ */
private $billing_agreements_endpoint; private $billing_agreements_endpoint;
/**
* The subscription helper
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/** /**
* The logger. * The logger.
* *
@ -185,6 +193,7 @@ class SettingsListener {
* @param string $partner_merchant_id_production Partner merchant ID production. * @param string $partner_merchant_id_production Partner merchant ID production.
* @param string $partner_merchant_id_sandbox Partner merchant ID sandbox. * @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
* @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint. * @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param ?LoggerInterface $logger The logger. * @param ?LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
@ -203,6 +212,7 @@ class SettingsListener {
string $partner_merchant_id_production, string $partner_merchant_id_production,
string $partner_merchant_id_sandbox, string $partner_merchant_id_sandbox,
BillingAgreementsEndpoint $billing_agreements_endpoint, BillingAgreementsEndpoint $billing_agreements_endpoint,
SubscriptionHelper $subscription_helper,
LoggerInterface $logger = null LoggerInterface $logger = null
) { ) {
@ -221,6 +231,7 @@ class SettingsListener {
$this->partner_merchant_id_production = $partner_merchant_id_production; $this->partner_merchant_id_production = $partner_merchant_id_production;
$this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox; $this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
$this->billing_agreements_endpoint = $billing_agreements_endpoint; $this->billing_agreements_endpoint = $billing_agreements_endpoint;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger ?: new NullLogger(); $this->logger = $logger ?: new NullLogger();
} }
@ -392,6 +403,11 @@ class SettingsListener {
$this->settings->persist(); $this->settings->persist();
} }
if ( $this->subscription_helper->plugin_is_active() ) {
$this->settings->set( 'blocks_final_review_enabled', true );
$this->settings->persist();
}
if ( $subscription_mode === 'disable_paypal_subscriptions' && $vault_enabled === '1' ) { if ( $subscription_mode === 'disable_paypal_subscriptions' && $vault_enabled === '1' ) {
$this->settings->set( 'vault_enabled', false ); $this->settings->set( 'vault_enabled', false );
$this->settings->persist(); $this->settings->persist();

View file

@ -185,6 +185,8 @@ If you encounter issues with the PayPal buttons not appearing after an update, p
* Fix - Allow PUI Gateway for refund processor #2192 * Fix - Allow PUI Gateway for refund processor #2192
* Fix - Notice on newly created block cart checkout #2211 * Fix - Notice on newly created block cart checkout #2211
* Fix - Apple Pay button in the editor #2177 * Fix - Apple Pay button in the editor #2177
* Fix - Allow shipping callback and skipping confirmation page from any express button #2236
* Enhancement - Use admin theme color #1602
= 2.7.0 - 2024-04-30 = = 2.7.0 - 2024-04-30 =
* Fix - Zero sum subscriptions cause CANNOT_BE_ZERO_OR_NEGATIVE when using Vault v3 #2152 * Fix - Zero sum subscriptions cause CANNOT_BE_ZERO_OR_NEGATIVE when using Vault v3 #2152

View file

@ -3,16 +3,15 @@
namespace WooCommerce\PayPalCommerce\WcGateway\Settings; namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Requests_Utility_CaseInsensitiveDictionary;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Helper\RedirectorStub; use WooCommerce\PayPalCommerce\Helper\RedirectorStub;
use WooCommerce\PayPalCommerce\Helper\StubRedirectionException;
use WooCommerce\PayPalCommerce\ModularTestCase; use WooCommerce\PayPalCommerce\ModularTestCase;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use Mockery; use Mockery;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar; use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
use function Brain\Monkey\Functions\when; use function Brain\Monkey\Functions\when;
@ -42,6 +41,7 @@ class SettingsListenerTest extends ModularTestCase
$pui_status_cache = Mockery::mock(Cache::class); $pui_status_cache = Mockery::mock(Cache::class);
$dcc_status_cache = Mockery::mock(Cache::class); $dcc_status_cache = Mockery::mock(Cache::class);
$billing_agreement_endpoint = Mockery::mock(BillingAgreementsEndpoint::class); $billing_agreement_endpoint = Mockery::mock(BillingAgreementsEndpoint::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$logger = Mockery::mock(LoggerInterface::class); $logger = Mockery::mock(LoggerInterface::class);
$testee = new SettingsListener( $testee = new SettingsListener(
@ -60,6 +60,7 @@ class SettingsListenerTest extends ModularTestCase
'', '',
'', '',
$billing_agreement_endpoint, $billing_agreement_endpoint,
$subscription_helper,
$logger $logger
); );