Merge pull request #2259 from woocommerce/PCP-3136-enable-shipping-callback-for-wc-subscriptions

Enable shipping callback for WC subscriptions (3136)
This commit is contained in:
Emili Castells 2024-06-03 14:25:36 +02:00 committed by GitHub
commit 3aba8d7dea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 211 additions and 91 deletions

View file

@ -50,9 +50,7 @@ class ShippingOptionFactory {
$cart->calculate_shipping();
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() );
if ( ! is_array( $chosen_shipping_methods ) ) {
$chosen_shipping_methods = array();
}
$chosen_shipping_method = $chosen_shipping_methods[0] ?? false;
$packages = WC()->shipping()->get_packages();
$options = array();
@ -62,11 +60,10 @@ class ShippingOptionFactory {
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 ),
$rate->get_id() === $chosen_shipping_method,
new Money(
(float) $rate->get_cost(),
get_woocommerce_currency()

View file

@ -51,16 +51,7 @@ return array(
);
}
$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 );
$should_disable_checkbox = apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false );
return $insert_after(
$fields,

View file

@ -398,6 +398,21 @@ const PayPalComponent = ({
await shippingData.setShippingAddress(address);
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);

View file

@ -23,7 +23,8 @@ class CartActionHandler {
body: JSON.stringify({
nonce: this.config.ajax.approve_subscription.nonce,
order_id: data.orderID,
subscription_id: data.subscriptionID
subscription_id: data.subscriptionID,
should_create_wc_order: !context.config.vaultingEnabled || data.paymentSource !== 'venmo'
})
}).then((res)=>{
return res.json();
@ -33,7 +34,9 @@ class CartActionHandler {
throw Error(data.data.message);
}
location.href = this.config.redirect;
let orderReceivedUrl = data.data?.order_received_url
location.href = orderReceivedUrl ? orderReceivedUrl : context.config.redirect;
});
},
onError: (err) => {

View file

@ -39,19 +39,21 @@ export const handleShippingOptionsChange = async (data, actions, config) => {
})
}
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,
})
});
if (!config.data_client_id.has_subscriptions) {
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();
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
if (!json.success) {
throw new Error(json.data.message);
}
}
} catch (e) {
console.error(e);
@ -104,20 +106,20 @@ export const handleShippingAddressChange = async (data, actions, config) => {
})
})
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 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();
const json = await res.json();
if (!json.success) {
throw new Error(json.data.message);
}
if (!json.success) {
throw new Error(json.data.message);
}
} catch (e) {
console.error(e);

View file

@ -239,7 +239,6 @@ return array(
$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(
$request_data,
@ -252,7 +251,6 @@ return array(
$final_review_enabled,
$gateway,
$wc_order_creator,
$subscription_helper,
$logger
);
},
@ -260,7 +258,10 @@ return array(
return new ApproveSubscriptionEndpoint(
$container->get( 'button.request-data' ),
$container->get( 'api.endpoint.order' ),
$container->get( 'session.handler' )
$container->get( 'session.handler' ),
$container->get( 'blocks.settings.final_review_enabled' ),
$container->get( 'button.helper.wc-order-creator' ),
$container->get( 'wcgateway.paypal-gateway' )
);
},
'button.checkout-form-saver' => static function ( ContainerInterface $container ): CheckoutFormSaver {
@ -362,6 +363,10 @@ return array(
},
'button.helper.wc-order-creator' => static function ( ContainerInterface $container ): WooCommerceOrderCreator {
return new WooCommerceOrderCreator( $container->get( 'wcgateway.funding-source.renderer' ), $container->get( 'session.handler' ) );
return new WooCommerceOrderCreator(
$container->get( 'wcgateway.funding-source.renderer' ),
$container->get( 'session.handler' ),
$container->get( 'wc-subscriptions.helper' )
);
},
);

View file

@ -105,13 +105,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
*/
protected $wc_order_creator;
/**
* The Subscription Helper.
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* The logger.
*
@ -132,7 +125,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
* @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(
@ -146,7 +138,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
bool $final_review_enabled,
PayPalGateway $gateway,
WooCommerceOrderCreator $wc_order_creator,
SubscriptionHelper $subscription_helper,
LoggerInterface $logger
) {
@ -160,7 +151,6 @@ class ApproveOrderEndpoint implements EndpointInterface {
$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;
}
@ -247,7 +237,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
$this->session_handler->replace_order( $order );
if ( ! $this->subscription_helper->plugin_is_active() && apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false ) ) {
if ( apply_filters( 'woocommerce_paypal_payments_toggle_final_review_checkbox', false ) ) {
$this->toggle_final_review_enabled_setting();
}

View file

@ -11,13 +11,18 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Button\Helper\WooCommerceOrderCreator;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class ApproveSubscriptionEndpoint
*/
class ApproveSubscriptionEndpoint implements EndpointInterface {
use ContextTrait;
const ENDPOINT = 'ppc-approve-subscription';
/**
@ -41,21 +46,51 @@ class ApproveSubscriptionEndpoint implements EndpointInterface {
*/
private $session_handler;
/**
* Whether the final review is enabled.
*
* @var bool
*/
protected $final_review_enabled;
/**
* The WooCommerce order creator.
*
* @var WooCommerceOrderCreator
*/
protected $wc_order_creator;
/**
* The WC gateway.
*
* @var PayPalGateway
*/
protected $gateway;
/**
* ApproveSubscriptionEndpoint constructor.
*
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler.
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler.
* @param bool $final_review_enabled Whether the final review is enabled.
* @param WooCommerceOrderCreator $wc_order_creator The WooCommerce order creator.
* @param PayPalGateway $gateway The WC gateway.
*/
public function __construct(
RequestData $request_data,
OrderEndpoint $order_endpoint,
SessionHandler $session_handler
SessionHandler $session_handler,
bool $final_review_enabled,
WooCommerceOrderCreator $wc_order_creator,
PayPalGateway $gateway
) {
$this->request_data = $request_data;
$this->order_endpoint = $order_endpoint;
$this->session_handler = $session_handler;
$this->request_data = $request_data;
$this->order_endpoint = $order_endpoint;
$this->session_handler = $session_handler;
$this->final_review_enabled = $final_review_enabled;
$this->wc_order_creator = $wc_order_creator;
$this->gateway = $gateway;
}
/**
@ -88,6 +123,15 @@ class ApproveSubscriptionEndpoint implements EndpointInterface {
WC()->session->set( 'ppcp_subscription_id', $data['subscription_id'] );
}
$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();
return true;
}

View file

@ -14,12 +14,16 @@ use WC_Cart;
use WC_Order;
use WC_Order_Item_Product;
use WC_Order_Item_Shipping;
use WC_Subscription;
use WC_Subscriptions_Product;
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;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WP_Error;
/**
* Class WooCommerceOrderCreator
@ -40,18 +44,28 @@ class WooCommerceOrderCreator {
*/
protected $session_handler;
/**
* The subscription helper
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* WooCommerceOrderCreator constructor.
*
* @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
* @param SessionHandler $session_handler The session handler.
* @param SubscriptionHelper $subscription_helper The subscription helper.
*/
public function __construct(
FundingSourceRenderer $funding_source_renderer,
SessionHandler $session_handler
SessionHandler $session_handler,
SubscriptionHelper $subscription_helper
) {
$this->funding_source_renderer = $funding_source_renderer;
$this->session_handler = $session_handler;
$this->subscription_helper = $subscription_helper;
}
/**
@ -69,10 +83,13 @@ class WooCommerceOrderCreator {
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() );
$payer = $order->payer();
$shipping = $order->purchase_units()[0]->shipping();
$this->configure_payment_source( $wc_order );
$this->configure_customer( $wc_order );
$this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping );
$this->configure_shipping( $wc_order, $payer, $shipping );
$this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() );
$wc_order->calculate_totals();
@ -84,11 +101,13 @@ class WooCommerceOrderCreator {
/**
* Configures the line items.
*
* @param WC_Order $wc_order The WC order.
* @param WC_Cart $wc_cart The Cart.
* @param WC_Order $wc_order The WC order.
* @param WC_Cart $wc_cart The Cart.
* @param Payer|null $payer The payer.
* @param Shipping|null $shipping The shipping.
* @return void
*/
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart ): void {
protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void {
$cart_contents = $wc_cart->get_cart();
foreach ( $cart_contents as $cart_item ) {
@ -111,9 +130,37 @@ class WooCommerceOrderCreator {
return;
}
$total = $product->get_price() * $quantity;
$item->set_name( $product->get_name() );
$item->set_subtotal( $product->get_price() * $quantity );
$item->set_total( $product->get_price() * $quantity );
$item->set_subtotal( $total );
$item->set_total( $total );
$product_id = $product->get_id();
if ( $this->is_subscription( $product_id ) ) {
$subscription = $this->create_subscription( $wc_order, $product_id );
$sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product );
$subscription_total = $total + $sign_up_fee;
$item->set_subtotal( $subscription_total );
$item->set_total( $subscription_total );
$subscription->add_product( $product );
$this->configure_shipping( $subscription, $payer, $shipping );
$this->configure_payment_source( $subscription );
$this->configure_coupons( $subscription, $wc_cart->get_applied_coupons() );
$dates = array(
'trial_end' => WC_Subscriptions_Product::get_trial_expiration_date( $product_id ),
'next_payment' => WC_Subscriptions_Product::get_first_renewal_payment_date( $product_id ),
'end' => WC_Subscriptions_Product::get_expiration_date( $product_id ),
);
$subscription->update_dates( $dates );
$subscription->calculate_totals();
$subscription->payment_complete_for_order( $wc_order );
}
$wc_order->add_item( $item );
}
@ -179,6 +226,14 @@ class WooCommerceOrderCreator {
$shipping->set_method_id( $shipping_options->id() );
$shipping->set_total( $shipping_options->amount()->value_str() );
$items = $wc_order->get_items();
$items_in_package = array();
foreach ( $items as $item ) {
$items_in_package[] = $item->get_name() . ' &times; ' . (string) $item->get_quantity();
}
$shipping->add_meta_data( __( 'Items', 'woocommerce-paypal-payments' ), implode( ', ', $items_in_package ) );
$wc_order->add_item( $shipping );
}
}
@ -225,4 +280,43 @@ class WooCommerceOrderCreator {
}
}
/**
* Checks if the product with given ID is WC subscription.
*
* @param int $product_id The product ID.
* @return bool true if the product is subscription, otherwise false.
*/
protected function is_subscription( int $product_id ): bool {
if ( ! $this->subscription_helper->plugin_is_active() ) {
return false;
}
return WC_Subscriptions_Product::is_subscription( $product_id );
}
/**
* Creates WC subscription from given order and product ID.
*
* @param WC_Order $wc_order The WC order.
* @param int $product_id The product ID.
* @return WC_Subscription The subscription order
* @throws RuntimeException If problem creating.
*/
protected function create_subscription( WC_Order $wc_order, int $product_id ): WC_Subscription {
$subscription = wcs_create_subscription(
array(
'order_id' => $wc_order->get_id(),
'status' => 'pending',
'billing_period' => WC_Subscriptions_Product::get_period( $product_id ),
'billing_interval' => WC_Subscriptions_Product::get_interval( $product_id ),
'customer_id' => $wc_order->get_customer_id(),
)
);
if ( $subscription instanceof WP_Error ) {
throw new RuntimeException( $subscription->get_error_message() );
}
return $subscription;
}
}

View file

@ -347,7 +347,6 @@ return array(
$container->get( 'api.partner_merchant_id-production' ),
$container->get( 'api.partner_merchant_id-sandbox' ),
$container->get( 'api.endpoint.billing-agreements' ),
$container->get( 'wc-subscriptions.helper' ),
$logger
);
},

View file

@ -10,8 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
@ -23,9 +21,7 @@ use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
/**
@ -161,13 +157,6 @@ class SettingsListener {
*/
private $billing_agreements_endpoint;
/**
* The subscription helper
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* The logger.
*
@ -193,7 +182,6 @@ class SettingsListener {
* @param string $partner_merchant_id_production Partner merchant ID production.
* @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
* @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param ?LoggerInterface $logger The logger.
*/
public function __construct(
@ -212,7 +200,6 @@ class SettingsListener {
string $partner_merchant_id_production,
string $partner_merchant_id_sandbox,
BillingAgreementsEndpoint $billing_agreements_endpoint,
SubscriptionHelper $subscription_helper,
LoggerInterface $logger = null
) {
@ -231,7 +218,6 @@ class SettingsListener {
$this->partner_merchant_id_production = $partner_merchant_id_production;
$this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
$this->billing_agreements_endpoint = $billing_agreements_endpoint;
$this->subscription_helper = $subscription_helper;
$this->logger = $logger ?: new NullLogger();
}
@ -414,11 +400,6 @@ class SettingsListener {
$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' ) {
$this->settings->set( 'vault_enabled', false );
$this->settings->persist();

View file

@ -60,7 +60,6 @@ class SettingsListenerTest extends ModularTestCase
'',
'',
$billing_agreement_endpoint,
$subscription_helper,
$logger
);