Merge trunk

This commit is contained in:
dinamiko 2022-06-14 15:03:19 +02:00
commit e153e2be35
18 changed files with 208 additions and 35 deletions

View file

@ -1,5 +1,16 @@
*** Changelog *** *** Changelog ***
= 1.8.2 - TBD =
* Fix - Order not approved: payment via vaulted PayPal account fails #677
* Fix - Something went wrong error in Virtual products when using vaulted payment #673
* Fix - PayPal smart buttons are not displayed for product variations when parent product is set to out of stock #669
* Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667
* Fix - "Capture Virtual-Only Orders" intent sets virtual+downloadable product orders to "Processing" instead of "Completed" #665
* Fix - Free trial period causing incorrerct disable-funding parameters with DCC disabled #661
* Fix - Smart button not visible on single product page when product price is below 1 and decimal is "," #654
* Fix - Checkout using an email address containing a + symbol results in a "[INVALID_REQUEST]" error #523
* Enhancement - Improve checkout validation & order creation #513
= 1.8.1 - 2022-05-31 = = 1.8.1 - 2022-05-31 =
* Fix - Manual orders return an error for guest users when paying with PayPal Card Processing #530 * Fix - Manual orders return an error for guest users when paying with PayPal Card Processing #530
* Fix - "No PayPal order found in the current WooCommerce session" error for guests on Pay for Order page #605 * Fix - "No PayPal order found in the current WooCommerce session" error for guests on Pay for Order page #605

View file

@ -47,6 +47,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\CartRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository;
@ -671,4 +672,7 @@ return array(
'SE', 'SE',
); );
}, },
'api.order-helper' => static function( ContainerInterface $container ): OrderHelper {
return new OrderHelper();
},
); );

View file

@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\PayerName;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PayerTaxInfo; use WooCommerce\PayPalCommerce\ApiClient\Entity\PayerTaxInfo;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Phone; use WooCommerce\PayPalCommerce\ApiClient\Entity\Phone;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PhoneWithType; use WooCommerce\PayPalCommerce\ApiClient\Entity\PhoneWithType;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/** /**
* Class PayerFactory * Class PayerFactory
@ -158,6 +159,7 @@ class PayerFactory {
* *
* @param array $form_fields The checkout form fields. * @param array $form_fields The checkout form fields.
* @return Payer * @return Payer
* @throws RuntimeException When invalid data.
*/ */
public function from_checkout_form( array $form_fields ): Payer { public function from_checkout_form( array $form_fields ): Payer {
@ -189,6 +191,14 @@ class PayerFactory {
} }
} }
if ( ! is_email( $billing_email ) ) {
/*
phpcs:disable WordPress.WP.I18n.TextDomainMismatch
translators: %s: email address
*/
throw new RuntimeException( sprintf( __( '%s is not a valid email address.', 'woocommerce' ), esc_html( $billing_email ) ) );
}
return new Payer( return new Payer(
new PayerName( $first_name, $last_name ), new PayerName( $first_name, $last_name ),
$billing_email, $billing_email,

View file

@ -0,0 +1,34 @@
<?php
/**
* PayPal order helper.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Helper
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Helper;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
/**
* Class OrderHelper
*/
class OrderHelper {
/**
* Checks if order contains physical goods.
*
* @param Order $order PayPal order.
* @return bool
*/
public function contains_physical_goods( Order $order ): bool {
foreach ( $order->purchase_units() as $unit ) {
if ( $unit->contains_physical_goods() ) {
return true;
}
}
return false;
}
}

View file

@ -31,6 +31,19 @@ const bootstrap = () => {
const onSmartButtonClick = (data, actions) => { const onSmartButtonClick = (data, actions) => {
window.ppcpFundingSource = data.fundingSource; window.ppcpFundingSource = data.fundingSource;
// TODO: quick fix to get the error about empty form before attempting PayPal order
// it should solve #513 for most of the users, but proper solution should be implemented later.
const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input');
requiredFields.each((i, input) => {
jQuery(input).trigger('validate');
});
if (jQuery('form.woocommerce-checkout .woocommerce-invalid').length) {
errorHandler.clear();
errorHandler.message(PayPalCommerceGateway.labels.error.js_validation);
return actions.reject();
}
const form = document.querySelector('form.woocommerce-checkout'); const form = document.querySelector('form.woocommerce-checkout');
if (form) { if (form) {
jQuery('#ppcp-funding-source-form-input').remove(); jQuery('#ppcp-funding-source-form-input').remove();

View file

@ -20,7 +20,9 @@ class CheckoutActionHandler {
const errorHandler = this.errorHandler; const errorHandler = this.errorHandler;
const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review'; const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review';
const formValues = jQuery(formSelector).serialize(); const formData = new FormData(document.querySelector(formSelector));
// will not handle fields with multiple values (checkboxes, <select multiple>), but we do not care about this here
const formJsonObj = Object.fromEntries(formData);
const createaccount = jQuery('#createaccount').is(":checked") ? true : false; const createaccount = jQuery('#createaccount').is(":checked") ? true : false;
@ -34,7 +36,7 @@ class CheckoutActionHandler {
order_id:this.config.order_id, order_id:this.config.order_id,
payment_method: getCurrentPaymentMethod(), payment_method: getCurrentPaymentMethod(),
funding_source: window.ppcpFundingSource, funding_source: window.ppcpFundingSource,
form:formValues, form: formJsonObj,
createaccount: createaccount createaccount: createaccount
}) })
}).then(function (res) { }).then(function (res) {
@ -59,7 +61,7 @@ class CheckoutActionHandler {
} }
} }
return; throw new Error(data.data.message);
} }
const input = document.createElement('input'); const input = document.createElement('input');
input.setAttribute('type', 'hidden'); input.setAttribute('type', 'hidden');

View file

@ -14,6 +14,7 @@ class SingleProductBootstap {
if (!this.shouldRender()) { if (!this.shouldRender()) {
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper); this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
this.renderer.hideButtons(this.gateway.button.wrapper); this.renderer.hideButtons(this.gateway.button.wrapper);
this.messages.hideMessages();
return; return;
} }
@ -26,6 +27,7 @@ class SingleProductBootstap {
if (!this.shouldRender()) { if (!this.shouldRender()) {
this.renderer.hideButtons(this.gateway.hosted_fields.wrapper); this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
this.messages.hideMessages();
return; return;
} }
@ -51,6 +53,8 @@ class SingleProductBootstap {
else if (document.querySelector('.product .woocommerce-Price-amount')) { else if (document.querySelector('.product .woocommerce-Price-amount')) {
priceText = document.querySelector('.product .woocommerce-Price-amount').innerText; priceText = document.querySelector('.product .woocommerce-Price-amount').innerText;
} }
priceText = priceText.replace(/,/g, '.');
const amount = parseFloat(priceText.replace(/([^\d,\.\s]*)/g, '')); const amount = parseFloat(priceText.replace(/([^\d,\.\s]*)/g, ''));
return amount === 0; return amount === 0;

View file

@ -53,5 +53,14 @@ class MessageRenderer {
} }
return true; return true;
} }
hideMessages() {
const domElement = document.querySelector(this.config.wrapper);
if (! domElement ) {
return false;
}
domElement.style.display = 'none';
return true;
}
} }
export default MessageRenderer; export default MessageRenderer;

View file

@ -151,6 +151,7 @@ return array(
$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' );
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $logger = $container->get( 'woocommerce.logger.woocommerce' );
return new ApproveOrderEndpoint( return new ApproveOrderEndpoint(
$request_data, $request_data,
@ -159,6 +160,7 @@ return array(
$three_d_secure, $three_d_secure,
$settings, $settings,
$dcc_applies, $dcc_applies,
$order_helper,
$logger $logger
); );
}, },

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button\Assets;
use Exception; use Exception;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use WC_Product; use WC_Product;
use WC_Product_Variation;
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;
@ -843,6 +844,10 @@ class SmartButton implements SmartButtonInterface {
'Something went wrong. Please try again or choose another payment source.', 'Something went wrong. Please try again or choose another payment source.',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
), ),
'js_validation' => __(
'Required form fields are not filled or invalid.',
'woocommerce-paypal-payments'
),
), ),
), ),
'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0, 'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0,
@ -922,9 +927,9 @@ class SmartButton implements SmartButtonInterface {
} }
if ( $this->is_free_trial_cart() ) { if ( $this->is_free_trial_cart() ) {
$all_sources = $this->all_funding_sources; $all_sources = array_keys( $this->all_funding_sources );
if ( $is_dcc_enabled ) { if ( $is_dcc_enabled ) {
$all_sources = array_keys( array_diff_key( $all_sources, array( 'card' => '' ) ) ); $all_sources = array_diff( $all_sources, array( 'card' ) );
} }
$disable_funding = $all_sources; $disable_funding = $all_sources;
} }
@ -1252,9 +1257,25 @@ class SmartButton implements SmartButtonInterface {
/** /**
* The filter returning true if PayPal buttons/messages can be rendered for this product, or false otherwise. * The filter returning true if PayPal buttons/messages can be rendered for this product, or false otherwise.
*/ */
$in_stock = $product->is_in_stock();
if ( $product->is_type( 'variable' ) ) {
/**
* The method is defined in WC_Product_Variable class.
*
* @psalm-suppress UndefinedMethod
*/
$variations = $product->get_available_variations( 'objects' );
$in_stock = $this->has_in_stock_variation( $variations );
}
/**
* Allows to filter if PayPal buttons/messages can be rendered for the given product.
*/
return apply_filters( return apply_filters(
'woocommerce_paypal_payments_product_supports_payment_request_button', 'woocommerce_paypal_payments_product_supports_payment_request_button',
! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), ! $product->is_type( array( 'external', 'grouped' ) ) && $in_stock,
$product $product
); );
} }
@ -1291,4 +1312,20 @@ class SmartButton implements SmartButtonInterface {
} }
return ''; return '';
} }
/**
* Checks if variations contain any in stock variation.
*
* @param WC_Product_Variation[] $variations The list of variations.
* @return bool True if any in stock variation, false otherwise.
*/
protected function has_in_stock_variation( array $variations ): bool {
foreach ( $variations as $variation ) {
if ( $variation->is_in_stock() ) {
return true;
}
}
return false;
}
} }

View file

@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
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\Button\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
@ -26,7 +27,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/ */
class ApproveOrderEndpoint implements EndpointInterface { class ApproveOrderEndpoint implements EndpointInterface {
const ENDPOINT = 'ppc-approve-order'; const ENDPOINT = 'ppc-approve-order';
/** /**
@ -71,6 +71,13 @@ class ApproveOrderEndpoint implements EndpointInterface {
*/ */
private $dcc_applies; private $dcc_applies;
/**
* The order helper.
*
* @var OrderHelper
*/
protected $order_helper;
/** /**
* The logger. * The logger.
* *
@ -87,6 +94,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
* @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 LoggerInterface $logger The logger. * @param LoggerInterface $logger The logger.
*/ */
public function __construct( public function __construct(
@ -96,6 +104,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
ThreeDSecure $three_d_secure, ThreeDSecure $three_d_secure,
Settings $settings, Settings $settings,
DccApplies $dcc_applies, DccApplies $dcc_applies,
OrderHelper $order_helper,
LoggerInterface $logger LoggerInterface $logger
) { ) {
@ -105,6 +114,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
$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->logger = $logger; $this->logger = $logger;
} }
@ -173,10 +183,10 @@ class ApproveOrderEndpoint implements EndpointInterface {
wp_send_json_success( $order ); wp_send_json_success( $order );
} }
if ( ! $order->status()->is( OrderStatus::APPROVED ) ) { if ( $this->order_helper->contains_physical_goods( $order ) && ! $order->status()->is( OrderStatus::APPROVED ) && ! $order->status()->is( OrderStatus::CREATED ) ) {
$message = sprintf( $message = sprintf(
// translators: %s is the id of the order. // translators: %s is the id of the order.
__( 'Order %s is not approved yet.', 'woocommerce-paypal-payments' ), __( 'Order %s is not ready for processing yet.', 'woocommerce-paypal-payments' ),
$data['order_id'] $data['order_id']
); );

View file

@ -403,9 +403,9 @@ class CreateOrderEndpoint implements EndpointInterface {
} }
if ( ! $payer && isset( $data['form'] ) ) { if ( ! $payer && isset( $data['form'] ) ) {
parse_str( $data['form'], $form_fields ); $form_fields = $data['form'];
if ( isset( $form_fields['billing_email'] ) && '' !== $form_fields['billing_email'] ) { if ( is_array( $form_fields ) && isset( $form_fields['billing_email'] ) && '' !== $form_fields['billing_email'] ) {
return $this->payer_factory->from_checkout_form( $form_fields ); return $this->payer_factory->from_checkout_form( $form_fields );
} }
} }

View file

@ -81,15 +81,9 @@ class RequestData {
$data = array(); $data = array();
foreach ( (array) $assoc_array as $raw_key => $raw_value ) { foreach ( (array) $assoc_array as $raw_key => $raw_value ) {
if ( ! is_array( $raw_value ) ) { if ( ! is_array( $raw_value ) ) {
/** // Not sure if it is a good idea to sanitize everything at this level,
* The 'form' key is preserved for url encoded data and needs different // but should be fine for now since we do not send any HTML or multi-line texts via ajax.
* sanitization.
*/
if ( 'form' !== $raw_key ) {
$data[ sanitize_text_field( (string) $raw_key ) ] = sanitize_text_field( (string) $raw_value ); $data[ sanitize_text_field( (string) $raw_key ) ] = sanitize_text_field( (string) $raw_value );
} else {
$data[ sanitize_text_field( (string) $raw_key ) ] = sanitize_text_field( urldecode( (string) $raw_value ) );
}
continue; continue;
} }
$data[ sanitize_text_field( (string) $raw_key ) ] = $this->sanitize( $raw_value ); $data[ sanitize_text_field( (string) $raw_key ) ] = $this->sanitize( $raw_value );

View file

@ -232,6 +232,7 @@ return array(
$environment = $container->get( 'onboarding.environment' ); $environment = $container->get( 'onboarding.environment' );
$logger = $container->get( 'woocommerce.logger.woocommerce' ); $logger = $container->get( 'woocommerce.logger.woocommerce' );
$subscription_helper = $container->get( 'subscription.helper' ); $subscription_helper = $container->get( 'subscription.helper' );
$order_helper = $container->get( 'api.order-helper' );
return new OrderProcessor( return new OrderProcessor(
$session_handler, $session_handler,
$order_endpoint, $order_endpoint,
@ -241,7 +242,8 @@ return array(
$settings, $settings,
$logger, $logger,
$environment, $environment,
$subscription_helper $subscription_helper,
$order_helper
); );
}, },
'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor { 'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor {

View file

@ -14,6 +14,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
@ -106,6 +107,13 @@ class OrderProcessor {
*/ */
private $subscription_helper; private $subscription_helper;
/**
* The order helper.
*
* @var OrderHelper
*/
private $order_helper;
/** /**
* OrderProcessor constructor. * OrderProcessor constructor.
* *
@ -118,6 +126,7 @@ class OrderProcessor {
* @param LoggerInterface $logger A logger service. * @param LoggerInterface $logger A logger service.
* @param Environment $environment The environment. * @param Environment $environment The environment.
* @param SubscriptionHelper $subscription_helper The subscription helper. * @param SubscriptionHelper $subscription_helper The subscription helper.
* @param OrderHelper $order_helper The order helper.
*/ */
public function __construct( public function __construct(
SessionHandler $session_handler, SessionHandler $session_handler,
@ -128,7 +137,8 @@ class OrderProcessor {
Settings $settings, Settings $settings,
LoggerInterface $logger, LoggerInterface $logger,
Environment $environment, Environment $environment,
SubscriptionHelper $subscription_helper SubscriptionHelper $subscription_helper,
OrderHelper $order_helper
) { ) {
$this->session_handler = $session_handler; $this->session_handler = $session_handler;
@ -140,6 +150,7 @@ class OrderProcessor {
$this->environment = $environment; $this->environment = $environment;
$this->logger = $logger; $this->logger = $logger;
$this->subscription_helper = $subscription_helper; $this->subscription_helper = $subscription_helper;
$this->order_helper = $order_helper;
} }
/** /**
@ -160,9 +171,9 @@ class OrderProcessor {
$this->add_paypal_meta( $wc_order, $order, $this->environment ); $this->add_paypal_meta( $wc_order, $order, $this->environment );
$error_message = null; $error_message = null;
if ( ! $this->order_is_approved( $order ) ) { if ( $this->order_helper->contains_physical_goods( $order ) && ! $this->order_is_ready_for_process( $order ) ) {
$error_message = __( $error_message = __(
'The payment has not been approved yet.', 'The payment is not ready for processing yet.',
'woocommerce-paypal-payments' 'woocommerce-paypal-payments'
); );
} }
@ -204,7 +215,7 @@ class OrderProcessor {
__( 'Payment successfully captured.', 'woocommerce-paypal-payments' ) __( 'Payment successfully captured.', 'woocommerce-paypal-payments' )
); );
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' ); $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' );
$wc_order->update_status( 'processing' ); $wc_order->update_status( 'completed' );
} }
$this->last_error = ''; $this->last_error = '';
return true; return true;
@ -269,15 +280,15 @@ class OrderProcessor {
} }
/** /**
* Whether a given order is approved. * Whether a given order is ready for processing.
* *
* @param Order $order The order. * @param Order $order The order.
* *
* @return bool * @return bool
*/ */
private function order_is_approved( Order $order ): bool { private function order_is_ready_for_process( Order $order ): bool {
if ( $order->status()->is( OrderStatus::APPROVED ) ) { if ( $order->status()->is( OrderStatus::APPROVED ) || $order->status()->is( OrderStatus::CREATED ) ) {
return true; return true;
} }
@ -285,7 +296,7 @@ class OrderProcessor {
return false; return false;
} }
$is_approved = in_array( return in_array(
$this->threed_secure->proceed_with_order( $order ), $this->threed_secure->proceed_with_order( $order ),
array( array(
ThreeDSecure::NO_DECISION, ThreeDSecure::NO_DECISION,
@ -293,6 +304,5 @@ class OrderProcessor {
), ),
true true
); );
return $is_approved;
} }
} }

View file

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

View file

@ -1,5 +1,5 @@
=== WooCommerce PayPal Payments === === WooCommerce PayPal Payments ===
Contributors: woocommerce, automattic Contributors: woocommerce, automattic, inpsyde
Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell, shop, shopping, cart, checkout Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell, shop, shopping, cart, checkout
Requires at least: 5.3 Requires at least: 5.3
Tested up to: 6.0 Tested up to: 6.0
@ -81,6 +81,17 @@ Follow the steps below to connect the plugin to your PayPal account:
== Changelog == == Changelog ==
= 1.8.2 =
* Fix - Order not approved: payment via vaulted PayPal account fails #677
* Fix - Something went wrong error in Virtual products when using vaulted payment #673
* Fix - PayPal smart buttons are not displayed for product variations when parent product is set to out of stock #669
* Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667
* Fix - "Capture Virtual-Only Orders" intent sets virtual+downloadable product orders to "Processing" instead of "Completed" #665
* Fix - Free trial period causing incorrerct disable-funding parameters with DCC disabled #661
* Fix - Smart button not visible on single product page when product price is below 1 and decimal is "," #654
* Fix - Checkout using an email address containing a + symbol results in a "[INVALID_REQUEST]" error #523
* Enhancement - Improve checkout validation & order creation #513
= 1.8.1 = = 1.8.1 =
* Fix - Manual orders return an error for guest users when paying with PayPal Card Processing #530 * Fix - Manual orders return an error for guest users when paying with PayPal Card Processing #530
* Fix - "No PayPal order found in the current WooCommerce session" error for guests on Pay for Order page #605 * Fix - "No PayPal order found in the current WooCommerce session" error for guests on Pay for Order page #605

View file

@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
@ -129,6 +130,8 @@ class OrderProcessorTest extends TestCase
$subscription_helper = Mockery::mock(SubscriptionHelper::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class);
$subscription_helper->shouldReceive('has_subscription'); $subscription_helper->shouldReceive('has_subscription');
$order_helper = Mockery::mock(OrderHelper::class);
$testee = new OrderProcessor( $testee = new OrderProcessor(
$sessionHandler, $sessionHandler,
$orderEndpoint, $orderEndpoint,
@ -138,7 +141,8 @@ class OrderProcessorTest extends TestCase
$settings, $settings,
$logger, $logger,
$this->environment, $this->environment,
$subscription_helper $subscription_helper,
$order_helper
); );
$wcOrder $wcOrder
@ -166,6 +170,8 @@ class OrderProcessorTest extends TestCase
->with($transactionId); ->with($transactionId);
$wcOrder->shouldReceive('save'); $wcOrder->shouldReceive('save');
$order_helper->shouldReceive('contains_physical_goods')->andReturn(true);
$this->assertTrue($testee->process($wcOrder)); $this->assertTrue($testee->process($wcOrder));
} }
@ -248,6 +254,8 @@ class OrderProcessorTest extends TestCase
$logger = Mockery::mock(LoggerInterface::class); $logger = Mockery::mock(LoggerInterface::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class);
$order_helper = Mockery::mock(OrderHelper::class);
$testee = new OrderProcessor( $testee = new OrderProcessor(
$sessionHandler, $sessionHandler,
$orderEndpoint, $orderEndpoint,
@ -257,7 +265,8 @@ class OrderProcessorTest extends TestCase
$settings, $settings,
$logger, $logger,
$this->environment, $this->environment,
$subscription_helper $subscription_helper,
$order_helper
); );
$wcOrder $wcOrder
@ -280,6 +289,8 @@ class OrderProcessorTest extends TestCase
->expects('payment_complete'); ->expects('payment_complete');
$wcOrder->shouldReceive('save'); $wcOrder->shouldReceive('save');
$order_helper->shouldReceive('contains_physical_goods')->andReturn(true);
$this->assertTrue($testee->process($wcOrder)); $this->assertTrue($testee->process($wcOrder));
} }
@ -309,6 +320,10 @@ class OrderProcessorTest extends TestCase
->expects('is') ->expects('is')
->with(OrderStatus::APPROVED) ->with(OrderStatus::APPROVED)
->andReturn(false); ->andReturn(false);
$orderStatus
->expects('is')
->with(OrderStatus::CREATED)
->andReturn(false);
$orderId = 'abc'; $orderId = 'abc';
$orderIntent = 'CAPTURE'; $orderIntent = 'CAPTURE';
$currentOrder = Mockery::mock(Order::class); $currentOrder = Mockery::mock(Order::class);
@ -346,6 +361,8 @@ class OrderProcessorTest extends TestCase
$logger = Mockery::mock(LoggerInterface::class); $logger = Mockery::mock(LoggerInterface::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class);
$order_helper = Mockery::mock(OrderHelper::class);
$testee = new OrderProcessor( $testee = new OrderProcessor(
$sessionHandler, $sessionHandler,
$orderEndpoint, $orderEndpoint,
@ -355,7 +372,8 @@ class OrderProcessorTest extends TestCase
$settings, $settings,
$logger, $logger,
$this->environment, $this->environment,
$subscription_helper $subscription_helper,
$order_helper
); );
$wcOrder $wcOrder
@ -372,6 +390,8 @@ class OrderProcessorTest extends TestCase
); );
$wcOrder->shouldReceive('save'); $wcOrder->shouldReceive('save');
$order_helper->shouldReceive('contains_physical_goods')->andReturn(true);
$this->assertFalse($testee->process($wcOrder)); $this->assertFalse($testee->process($wcOrder));
$this->assertNotEmpty($testee->last_error()); $this->assertNotEmpty($testee->last_error());
} }