Fix merge conflicts

This commit is contained in:
Emili Castells Guasch 2023-05-16 12:44:14 +02:00
commit 3c0e807758
55 changed files with 5170 additions and 202 deletions

View file

@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PlanFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
@ -320,12 +321,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

@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
@ -180,6 +181,8 @@ class OrderEndpoint {
* @param Payer|null $payer The payer off the order.
* @param PaymentToken|null $payment_token The payment token.
* @param PaymentMethod|null $payment_method The payment method.
* @param string $paypal_request_id The paypal request id.
* @param string $user_action The user action.
*
* @return Order
* @throws RuntimeException If the request fails.
@ -189,19 +192,28 @@ class OrderEndpoint {
string $shipping_preference,
Payer $payer = null,
PaymentToken $payment_token = null,
PaymentMethod $payment_method = null
PaymentMethod $payment_method = null,
string $paypal_request_id = '',
string $user_action = ApplicationContext::USER_ACTION_CONTINUE
): Order {
$bearer = $this->bearer->bearer();
$data = array(
'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) ? 'AUTHORIZE' : $this->intent,
'purchase_units' => array_map(
static function ( PurchaseUnit $item ): array {
return $item->to_array();
static function ( PurchaseUnit $item ) use ( $shipping_preference ): array {
$data = $item->to_array();
if ( $shipping_preference !== ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE ) {
// Shipping options are not allowed to be sent when not getting the address from PayPal.
unset( $data['shipping']['options'] );
}
return $data;
},
$items
),
'application_context' => $this->application_context_repository
->current_context( $shipping_preference )->to_array(),
->current_context( $shipping_preference, $user_action )->to_array(),
);
if ( $payer && ! empty( $payer->email_address() ) ) {
$data['payer'] = $payer->to_array();
@ -510,13 +522,22 @@ class OrderEndpoint {
return $order_to_update;
}
$this->patch( $order_to_update->id(), $patches );
$new_order = $this->order( $order_to_update->id() );
return $new_order;
}
/**
* Patches an order.
*
* @param string $order_id The PayPal order ID.
* @param PatchCollection $patches The patches.
*
* @throws RuntimeException If the request fails.
*/
public function patch( string $order_id, PatchCollection $patches ): void {
$patches_array = $patches->to_array();
if ( ! isset( $patches_array[0]['value']['shipping'] ) ) {
$shipping = isset( $order_to_update->purchase_units()[0] ) && null !== $order_to_update->purchase_units()[0]->shipping() ? $order_to_update->purchase_units()[0]->shipping() : null;
if ( $shipping ) {
$patches_array[0]['value']['shipping'] = $shipping->to_array();
}
}
/**
* The filter can be used to modify the order patching request body data (the final prices, items).
@ -524,7 +545,7 @@ class OrderEndpoint {
$patches_array = apply_filters( 'ppcp_patch_order_request_body_data', $patches_array );
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_to_update->id();
$url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_id;
$args = array(
'method' => 'PATCH',
'headers' => array(
@ -540,11 +561,8 @@ class OrderEndpoint {
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
$error = new RuntimeException(
__( 'Could not retrieve order.', 'woocommerce-paypal-payments' )
);
$this->logger->log(
'warning',
$error = new RuntimeException( 'Could not patch order.' );
$this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@ -560,8 +578,7 @@ class OrderEndpoint {
$json,
$status_code
);
$this->logger->log(
'warning',
$this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@ -570,9 +587,6 @@ class OrderEndpoint {
);
throw $error;
}
$new_order = $this->order( $order_to_update->id() );
return $new_order;
}
/**

View file

@ -83,8 +83,8 @@ class Patch {
public function to_array(): array {
return array(
'op' => $this->op(),
'value' => $this->value(),
'path' => $this->path(),
'value' => $this->value(),
);
}

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

@ -71,7 +71,15 @@ class PatchCollectionFactory {
);
$operation = $purchase_unit_from ? 'replace' : 'add';
$value = $purchase_unit_to->to_array();
$patches[] = new Patch(
if ( ! isset( $value['shipping'] ) ) {
$shipping = $purchase_unit_from && null !== $purchase_unit_from->shipping() ? $purchase_unit_from->shipping() : null;
if ( $shipping ) {
$value['shipping'] = $shipping->to_array();
}
}
$patches[] = new Patch(
$operation,
$path . "/@reference_id=='" . $purchase_unit_to->reference_id() . "'",
$value

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

@ -38,11 +38,13 @@ class ApplicationContextRepository {
* Returns the current application context.
*
* @param string $shipping_preferences The shipping preferences.
* @param string $user_action The user action.
*
* @return ApplicationContext
*/
public function current_context(
string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING
string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING,
string $user_action = ApplicationContext::USER_ACTION_CONTINUE
): ApplicationContext {
$brand_name = $this->settings->has( 'brand_name' ) ? $this->settings->get( 'brand_name' ) : '';
@ -55,7 +57,8 @@ class ApplicationContextRepository {
(string) $brand_name,
$locale,
(string) $landingpage,
$shipping_preferences
$shipping_preferences,
$user_action
);
return $context;
}