Show shipping methods in paypal if express without review

This commit is contained in:
Alex P 2023-04-25 15:06:24 +03:00
parent 8efa0e6da3
commit 9d638a57e3
No known key found for this signature in database
GPG key ID: 54487A734A204D71
12 changed files with 355 additions and 16 deletions

View file

@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
@ -289,12 +290,19 @@ return array(
);
},
'api.factory.shipping' => static function ( ContainerInterface $container ): ShippingFactory {
$address_factory = $container->get( 'api.factory.address' );
return new ShippingFactory( $address_factory );
return new ShippingFactory(
$container->get( 'api.factory.address' ),
$container->get( 'api.factory.shipping-option' )
);
},
'api.factory.shipping-preference' => static function ( ContainerInterface $container ): ShippingPreferenceFactory {
return new ShippingPreferenceFactory();
},
'api.factory.shipping-option' => static function ( ContainerInterface $container ): ShippingOptionFactory {
return new ShippingOptionFactory(
$container->get( 'api.factory.money' )
);
},
'api.factory.amount' => static function ( ContainerInterface $container ): AmountFactory {
$item_factory = $container->get( 'api.factory.item' );
return new AmountFactory(

View file

@ -28,15 +28,24 @@ class Shipping {
*/
private $address;
/**
* Shipping methods.
*
* @var ShippingOption[]
*/
private $options;
/**
* Shipping constructor.
*
* @param string $name The name.
* @param Address $address The address.
* @param string $name The name.
* @param Address $address The address.
* @param ShippingOption[] $options Shipping methods.
*/
public function __construct( string $name, Address $address ) {
public function __construct( string $name, Address $address, array $options = array() ) {
$this->name = $name;
$this->address = $address;
$this->options = $options;
}
/**
@ -57,17 +66,35 @@ class Shipping {
return $this->address;
}
/**
* Returns the shipping methods.
*
* @return ShippingOption[]
*/
public function options(): array {
return $this->options;
}
/**
* Returns the object as array.
*
* @return array
*/
public function to_array(): array {
return array(
$result = array(
'name' => array(
'full_name' => $this->name(),
),
'address' => $this->address()->to_array(),
);
if ( $this->options ) {
$result['options'] = array_map(
function ( ShippingOption $opt ): array {
return $opt->to_array();
},
$this->options
);
}
return $result;
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* The ShippingOption object.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
/**
* Class ShippingOption
*/
class ShippingOption {
const TYPE_SHIPPING = 'SHIPPING';
const TYPE_PICKUP = 'PICKUP';
/**
* The name.
*
* @var string
*/
private $id;
/**
* The label.
*
* @var string
*/
private $label;
/**
* Whether the method is selected by default.
*
* @var bool
*/
private $selected;
/**
* The price.
*
* @var Money
*/
private $amount;
/**
* SHIPPING or PICKUP.
*
* @var string
*/
private $type;
/**
* ShippingOption constructor.
*
* @param string $id The name.
* @param string $label The label.
* @param bool $selected Whether the method is selected by default.
* @param Money $amount The price.
* @param string $type SHIPPING or PICKUP.
*/
public function __construct( string $id, string $label, bool $selected, Money $amount, string $type ) {
$this->id = $id;
$this->label = $label;
$this->selected = $selected;
$this->amount = $amount;
$this->type = $type;
}
/**
* The name.
*
* @return string
*/
public function id(): string {
return $this->id;
}
/**
* The label.
*
* @return string
*/
public function label(): string {
return $this->label;
}
/**
* Whether the method is selected by default.
*
* @return bool
*/
public function selected(): bool {
return $this->selected;
}
/**
* Sets whether the method is selected by default.
*
* @param bool $selected The value to be set.
*/
public function set_selected( bool $selected ): void {
$this->selected = $selected;
}
/**
* The price.
*
* @return Money
*/
public function amount(): Money {
return $this->amount;
}
/**
* SHIPPING or PICKUP.
*
* @return string
*/
public function type(): string {
return $this->type;
}
/**
* Returns the object as array.
*
* @return array
*/
public function to_array(): array {
return array(
'id' => $this->id,
'label' => $this->label,
'selected' => $this->selected,
'amount' => $this->amount->to_array(),
'type' => $this->type,
);
}
}

View file

@ -153,10 +153,11 @@ class PurchaseUnitFactory {
* Creates a PurchaseUnit based off a WooCommerce cart.
*
* @param \WC_Cart|null $cart The cart.
* @param bool $with_shipping_options Include WC shipping methods.
*
* @return PurchaseUnit
*/
public function from_wc_cart( ?\WC_Cart $cart = null ): PurchaseUnit {
public function from_wc_cart( ?\WC_Cart $cart = null, bool $with_shipping_options = false ): PurchaseUnit {
if ( ! $cart ) {
$cart = WC()->cart ?? new \WC_Cart();
}
@ -172,7 +173,7 @@ class PurchaseUnitFactory {
$shipping = null;
$customer = \WC()->customer;
if ( $this->shipping_needed( ... array_values( $items ) ) && is_a( $customer, \WC_Customer::class ) ) {
$shipping = $this->shipping_factory->from_wc_customer( \WC()->customer );
$shipping = $this->shipping_factory->from_wc_customer( \WC()->customer, $with_shipping_options );
if (
2 !== strlen( $shipping->address()->country_code() ) ||
( ! $shipping->address()->postal_code() && ! $this->country_without_postal_code( $shipping->address()->country_code() ) )

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ShippingOption;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
@ -24,23 +25,33 @@ class ShippingFactory {
*/
private $address_factory;
/**
* The shipping option factory.
*
* @var ShippingOptionFactory
*/
private $shipping_option_factory;
/**
* ShippingFactory constructor.
*
* @param AddressFactory $address_factory The address factory.
* @param AddressFactory $address_factory The address factory.
* @param ShippingOptionFactory $shipping_option_factory The shipping option factory.
*/
public function __construct( AddressFactory $address_factory ) {
$this->address_factory = $address_factory;
public function __construct( AddressFactory $address_factory, ShippingOptionFactory $shipping_option_factory ) {
$this->address_factory = $address_factory;
$this->shipping_option_factory = $shipping_option_factory;
}
/**
* Creates a shipping object based off a WooCommerce customer.
*
* @param \WC_Customer $customer The WooCommerce customer.
* @param bool $with_shipping_options Include WC shipping methods.
*
* @return Shipping
*/
public function from_wc_customer( \WC_Customer $customer ): Shipping {
public function from_wc_customer( \WC_Customer $customer, bool $with_shipping_options = false ): Shipping {
// Replicates the Behavior of \WC_Order::get_formatted_shipping_full_name().
$full_name = sprintf(
// translators: %1$s is the first name and %2$s is the second name. wc translation.
@ -51,7 +62,8 @@ class ShippingFactory {
$address = $this->address_factory->from_wc_customer( $customer );
return new Shipping(
$full_name,
$address
$address,
$with_shipping_options ? $this->shipping_option_factory->from_wc_cart() : array()
);
}
@ -91,9 +103,14 @@ class ShippingFactory {
);
}
$address = $this->address_factory->from_paypal_response( $data->address );
$options = array_map(
array( $this->shipping_option_factory, 'from_paypal_response' ),
$data->options ?? array()
);
return new Shipping(
$data->name->full_name,
$address
$address,
$options
);
}
}

View file

@ -0,0 +1,111 @@
<?php
/**
* The shipping options factory.
*
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use stdClass;
use WC_Cart;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ShippingOption;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
* Class ShippingOptionFactory
*/
class ShippingOptionFactory {
/**
* The Money factory.
*
* @var MoneyFactory
*/
private $money_factory;
/**
* ShippingOptionFactory constructor.
*
* @param MoneyFactory $money_factory The Money factory.
*/
public function __construct( MoneyFactory $money_factory ) {
$this->money_factory = $money_factory;
}
/**
* Creates an array of ShippingOption objects for the shipping methods available in the cart.
*
* @param WC_Cart|null $cart The cart.
* @return ShippingOption[]
*/
public function from_wc_cart( ?WC_Cart $cart = null ): array {
if ( ! $cart ) {
$cart = WC()->cart ?? new WC_Cart();
}
$cart->calculate_shipping();
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() );
if ( ! is_array( $chosen_shipping_methods ) ) {
$chosen_shipping_methods = array();
}
$packages = WC()->shipping()->get_packages();
$options = array();
foreach ( $packages as $package ) {
$rates = $package['rates'] ?? array();
foreach ( $rates as $rate ) {
if ( ! $rate instanceof \WC_Shipping_Rate ) {
continue;
}
$options[] = new ShippingOption(
$rate->get_id(),
$rate->get_label(),
in_array( $rate->get_id(), $chosen_shipping_methods, true ),
new Money(
(float) $rate->get_cost(),
get_woocommerce_currency()
),
ShippingOption::TYPE_SHIPPING
);
}
}
if ( ! $chosen_shipping_methods && $options ) {
$options[0]->set_selected( true );
}
return $options;
}
/**
* Creates a ShippingOption object from the PayPal JSON object.
*
* @param stdClass $data The JSON object.
*
* @return ShippingOption
* @throws RuntimeException When JSON object is malformed.
*/
public function from_paypal_response( stdClass $data ): ShippingOption {
if ( ! isset( $data->id ) ) {
throw new RuntimeException( 'No id was given for shipping option.' );
}
if ( ! isset( $data->amount ) ) {
throw new RuntimeException( 'Shipping option amount not found' );
}
$amount = $this->money_factory->from_paypal_response( $data->amount );
return new ShippingOption(
$data->id,
$data->label ?? '',
isset( $data->selected ) ? (bool) $data->selected : false,
$amount,
$data->type ?? ShippingOption::TYPE_SHIPPING
);
}
}

View file

@ -63,4 +63,8 @@ return array(
return $contexts;
},
'button.handle-shipping-in-paypal' => function ( ContainerInterface $container ): bool {
return ! $container->get( 'blocks.settings.final_review_enabled' );
},
);

View file

@ -15,6 +15,7 @@ const PayPalComponent = ({
eventRegistration,
emitResponse,
activePaymentMethod,
shippingData,
}) => {
const {onPaymentSetup} = eventRegistration;
const {responseTypes} = emitResponse;
@ -129,6 +130,16 @@ const PayPalComponent = ({
onClick();
};
let handleShippingChange = null;
if (shippingData.needsShipping && !config.finalReviewEnabled) {
handleShippingChange = (data) => {
console.log(data)
const shippingOptionId = data.selected_shipping_option.id;
shippingData.setSelectedRates(shippingOptionId)
};
}
useEffect(() => {
if (activePaymentMethod !== config.id) {
return;
@ -187,6 +198,7 @@ const PayPalComponent = ({
onError={onClose}
createOrder={createOrder}
onApprove={handleApprove}
onShippingChange={handleShippingChange}
/>
);
}

View file

@ -161,6 +161,7 @@ return array(
$container->get( 'wcgateway.settings.card_billing_data_mode' ),
$container->get( 'button.early-wc-checkout-validation-enabled' ),
$container->get( 'button.pay-now-contexts' ),
$container->get( 'button.handle-shipping-in-paypal' ),
$logger
);
},
@ -271,4 +272,12 @@ return array(
'button.validation.wc-checkout-validator' => static function ( ContainerInterface $container ): CheckoutFormValidator {
return new CheckoutFormValidator();
},
/**
* If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
* May result in slower popup performance, additional loading.
*/
'button.handle-shipping-in-paypal' => static function ( ContainerInterface $container ): bool {
return false;
},
);

View file

@ -145,6 +145,13 @@ class CreateOrderEndpoint implements EndpointInterface {
*/
private $pay_now_contexts;
/**
* If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
*
* @var bool
*/
private $handle_shipping_in_paypal;
/**
* The logger.
*
@ -167,6 +174,7 @@ class CreateOrderEndpoint implements EndpointInterface {
* @param string $card_billing_data_mode The value of card_billing_data_mode from the settings.
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
* @param string[] $pay_now_contexts The contexts that should have the Pay Now button.
* @param bool $handle_shipping_in_paypal If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@ -182,6 +190,7 @@ class CreateOrderEndpoint implements EndpointInterface {
string $card_billing_data_mode,
bool $early_validation_enabled,
array $pay_now_contexts,
bool $handle_shipping_in_paypal,
LoggerInterface $logger
) {
@ -197,6 +206,7 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->card_billing_data_mode = $card_billing_data_mode;
$this->early_validation_enabled = $early_validation_enabled;
$this->pay_now_contexts = $pay_now_contexts;
$this->handle_shipping_in_paypal = $handle_shipping_in_paypal;
$this->logger = $logger;
}
@ -236,7 +246,7 @@ class CreateOrderEndpoint implements EndpointInterface {
}
$this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
} else {
$this->purchase_unit = $this->purchase_unit_factory->from_wc_cart();
$this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->handle_shipping_in_paypal );
// The cart does not have any info about payment method, so we must handle free trial here.
if ( (

View file

@ -294,7 +294,7 @@ class PurchaseUnitFactoryTest extends TestCase
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->expects('from_wc_customer')
->with($wcCustomer)
->with($wcCustomer, false)
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(

View file

@ -168,6 +168,7 @@ class CreateOrderEndpointTest extends TestCase
CardBillingMode::MINIMAL_INPUT,
false,
['checkout'],
false,
new NullLogger()
);
return array($payer_factory, $testee);