mirror of
https://gh.wpcy.net/https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2026-05-02 05:02:40 +08:00
351 lines
15 KiB
PHP
351 lines
15 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Can create WC orders.
|
|
*
|
|
* @package WooCommerce\PayPalCommerce\Button\Helper
|
|
*/
|
|
declare (strict_types=1);
|
|
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
|
|
|
use Exception;
|
|
use RuntimeException;
|
|
use WC_Cart;
|
|
use WC_Customer;
|
|
use WC_Data_Exception;
|
|
use WC_Order;
|
|
use WC_Order_Item_Product;
|
|
use WC_Order_Item_Shipping;
|
|
use WC_Product;
|
|
use WC_Subscription;
|
|
use WC_Subscriptions_Product;
|
|
use WC_Tax;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingFactory;
|
|
use WooCommerce\PayPalCommerce\Button\Session\CartData;
|
|
use WooCommerce\PayPalCommerce\Button\Session\CartDataFactory;
|
|
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
|
|
*/
|
|
class WooCommerceOrderCreator
|
|
{
|
|
/**
|
|
* The funding source renderer.
|
|
*
|
|
* @var FundingSourceRenderer
|
|
*/
|
|
protected $funding_source_renderer;
|
|
/**
|
|
* The Session handler.
|
|
*
|
|
* @var SessionHandler
|
|
*/
|
|
protected $session_handler;
|
|
/**
|
|
* The subscription helper
|
|
*
|
|
* @var SubscriptionHelper
|
|
*/
|
|
protected $subscription_helper;
|
|
protected CartDataFactory $cart_data_factory;
|
|
protected ShippingFactory $shipping_factory;
|
|
protected PayerFactory $payer_factory;
|
|
public function __construct(FundingSourceRenderer $funding_source_renderer, SessionHandler $session_handler, SubscriptionHelper $subscription_helper, CartDataFactory $cart_data_factory, ShippingFactory $shipping_factory, PayerFactory $payer_factory)
|
|
{
|
|
$this->funding_source_renderer = $funding_source_renderer;
|
|
$this->session_handler = $session_handler;
|
|
$this->subscription_helper = $subscription_helper;
|
|
$this->cart_data_factory = $cart_data_factory;
|
|
$this->shipping_factory = $shipping_factory;
|
|
$this->payer_factory = $payer_factory;
|
|
}
|
|
/**
|
|
* Creates WC order based on given PayPal order.
|
|
*
|
|
* @param Order $order The PayPal order.
|
|
* @param WC_Cart|CartData $cart The WC cart (converted into CartData).
|
|
* @param array|null $paypal_data The PayPal Response Data.
|
|
*
|
|
* @throws RuntimeException If problem creating.
|
|
*/
|
|
public function create_from_paypal_order(Order $order, $cart, ?array $paypal_data = null): WC_Order
|
|
{
|
|
$cart_data = $cart;
|
|
if ($cart_data instanceof WC_Cart) {
|
|
$cart_data = $this->cart_data_factory->from_current_cart($cart_data);
|
|
}
|
|
$wc_order = wc_create_order();
|
|
if (!$wc_order instanceof WC_Order) {
|
|
throw new RuntimeException('Problem creating WC order.');
|
|
}
|
|
try {
|
|
$payer = $this->get_payer($order, $paypal_data);
|
|
$shipping = $this->get_shipping($order, $paypal_data);
|
|
$this->configure_payment_source($wc_order);
|
|
$this->configure_customer($wc_order, $cart_data);
|
|
$this->configure_line_items($wc_order, $cart_data, $payer, $shipping);
|
|
$this->configure_addresses($wc_order, $payer, $shipping, $cart_data->needs_shipping());
|
|
$this->configure_coupons($wc_order, $cart_data->coupons());
|
|
$wc_order->calculate_totals();
|
|
$wc_order->save();
|
|
} catch (Exception $exception) {
|
|
$wc_order->delete(\true);
|
|
throw new RuntimeException('Failed to create WooCommerce order: ' . $exception->getMessage());
|
|
}
|
|
do_action('woocommerce_paypal_payments_woocommerce_order_created_from_cart', $wc_order, $cart_data);
|
|
return $wc_order;
|
|
}
|
|
/**
|
|
* Configures the line items.
|
|
*
|
|
* @psalm-suppress InvalidScalarArgument
|
|
*/
|
|
protected function configure_line_items(WC_Order $wc_order, CartData $cart_data, ?Payer $payer, ?Shipping $shipping): void
|
|
{
|
|
foreach ($cart_data->items() 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 (isset($cart_item['bundled_by'])) {
|
|
$item->add_meta_data('_bundled_by', $cart_item['bundled_by'], \true);
|
|
}
|
|
if (isset($cart_item['bundled_item_id'])) {
|
|
$item->add_meta_data('_bundled_item_id', $cart_item['bundled_item_id'], \true);
|
|
}
|
|
if (isset($cart_item['key'])) {
|
|
$item->add_meta_data('_bundle_cart_key', $cart_item['key'], \true);
|
|
}
|
|
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;
|
|
}
|
|
$subtotal = apply_filters('woocommerce_paypal_payments_shipping_callback_cart_line_item_total', $cart_item['line_subtotal'], $cart_item);
|
|
$item->set_name($product->get_name());
|
|
$item->set_subtotal($subtotal);
|
|
$item->set_total($cart_item['line_total']);
|
|
$this->configure_taxes($product, $item, $item->get_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 = (float) $subtotal + (float) $sign_up_fee;
|
|
$item->set_subtotal((string) $subscription_total);
|
|
$item->set_total((string) $subscription_total);
|
|
$subscription->add_product($product);
|
|
$this->configure_addresses($subscription, $payer, $shipping, $cart_data->needs_shipping());
|
|
$this->configure_payment_source($subscription);
|
|
$this->configure_coupons($subscription, $cart_data->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);
|
|
}
|
|
}
|
|
/**
|
|
* Configures the shipping & billing addresses for WC order from given payer.
|
|
*
|
|
* @throws WC_Data_Exception|RuntimeException When failing to configure shipping.
|
|
* @psalm-suppress RedundantConditionGivenDocblockType
|
|
*/
|
|
protected function configure_addresses(WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping, bool $needs_shipping): void
|
|
{
|
|
$shipping_address = null;
|
|
$billing_address = null;
|
|
$shipping_options = null;
|
|
$wc_customer = WC()->customer;
|
|
if (!$shipping && $needs_shipping) {
|
|
if ($wc_customer instanceof WC_Customer) {
|
|
$shipping = $this->shipping_factory->from_wc_customer($wc_customer, \true);
|
|
}
|
|
}
|
|
if ($payer) {
|
|
$address = $payer->address();
|
|
$payer_name = $payer->name();
|
|
$payer_phone = $payer->phone();
|
|
$wc_email = null;
|
|
if ($wc_customer instanceof WC_Customer) {
|
|
$wc_email = $wc_customer->get_email();
|
|
}
|
|
$email = $wc_email ?: $payer->email_address();
|
|
$billing_address = array('email' => $email ?: '', '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() : '', 'phone' => $payer_phone ? $payer_phone->phone()->national_number() : '');
|
|
}
|
|
if ($shipping) {
|
|
$address = $shipping->address();
|
|
if ($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 ($needs_shipping && empty($shipping_options)) {
|
|
throw new RuntimeException('No shipping method has been selected.');
|
|
}
|
|
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());
|
|
$items = $wc_order->get_items();
|
|
$items_in_package = array();
|
|
foreach ($items as $item) {
|
|
$items_in_package[] = $item->get_name() . ' × ' . (string) $item->get_quantity();
|
|
}
|
|
$shipping->add_meta_data(__('Items', 'woocommerce-paypal-payments'), implode(', ', $items_in_package));
|
|
$wc_order->add_item($shipping);
|
|
}
|
|
$wc_order->calculate_totals();
|
|
}
|
|
/**
|
|
* 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.
|
|
*/
|
|
protected function configure_customer(WC_Order $wc_order, CartData $cart_data): void
|
|
{
|
|
$current_user = wp_get_current_user();
|
|
if ($current_user->ID !== 0) {
|
|
$wc_order->set_customer_id($current_user->ID);
|
|
return;
|
|
}
|
|
$saved_user_id = $cart_data->user_id();
|
|
if ($saved_user_id !== 0) {
|
|
$wc_order->set_customer_id($saved_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);
|
|
}
|
|
}
|
|
/**
|
|
* Configures the taxes.
|
|
*
|
|
* @param WC_Product $product The Product.
|
|
* @param WC_Order_Item_Product $item The line item.
|
|
* @param float|string $subtotal The subtotal.
|
|
*
|
|
* @return void
|
|
* @psalm-suppress InvalidScalarArgument
|
|
*/
|
|
protected function configure_taxes(WC_Product $product, WC_Order_Item_Product $item, $subtotal): void
|
|
{
|
|
$tax_rates = WC_Tax::get_rates($product->get_tax_class());
|
|
$taxes = WC_Tax::calc_tax($subtotal, $tax_rates, \true);
|
|
$item->set_tax_class($product->get_tax_class());
|
|
$item->set_total_tax((string) array_sum($taxes));
|
|
}
|
|
/**
|
|
* 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;
|
|
}
|
|
/**
|
|
* Get the Payer from the PayPal order with fallback to the PayPal response data.
|
|
*
|
|
* @param Order $order The PayPal Order.
|
|
* @param array|null $paypal_data The PayPal Response data.
|
|
*
|
|
* @return Payer|null The Payer object or null if no payer information is available.
|
|
*/
|
|
private function get_payer(Order $order, ?array $paypal_data = null): ?Payer
|
|
{
|
|
$payer = $order->payer();
|
|
if (is_null($payer) && isset($paypal_data['payer'])) {
|
|
$payer_data = json_decode(wp_json_encode($paypal_data['payer']) ?: '');
|
|
$payer = $this->payer_factory->from_paypal_response($payer_data);
|
|
}
|
|
return $payer;
|
|
}
|
|
/**
|
|
* Get the Shipping information from the PayPal order with fallback to the PayPal response data.
|
|
*
|
|
* @param Order $order The PayPal Order.
|
|
* @param array|null $paypal_data The PayPal Response data.
|
|
*
|
|
* @return Shipping|null The shipping object or null if no shipping information is available.
|
|
*/
|
|
private function get_shipping(Order $order, ?array $paypal_data = null): ?Shipping
|
|
{
|
|
$purchase_units = $order->purchase_units();
|
|
$shipping = !empty($purchase_units) ? $purchase_units[0]->shipping() : null;
|
|
if ($shipping && is_null($shipping->address()) && isset($paypal_data['shipping_address'])) {
|
|
$paypal_data['shipping_address']['options'] = array_map(function ($option) {
|
|
return $option->to_array();
|
|
}, $shipping->options());
|
|
$shipping_address_data = json_decode(wp_json_encode($paypal_data['shipping_address']) ?: '');
|
|
$shipping = $this->shipping_factory->from_paypal_response($shipping_address_data);
|
|
}
|
|
return $shipping;
|
|
}
|
|
}
|