session_handler = $session_handler; $this->order_endpoint = $order_endpoint; $this->order_factory = $order_factory; $this->threed_secure = $three_d_secure; $this->authorized_payments_processor = $authorized_payments_processor; $this->settings = $settings; $this->environment = $environment; $this->logger = $logger; $this->subscription_helper = $subscription_helper; $this->order_helper = $order_helper; } /** * Processes a given WooCommerce order and captured/authorizes the connected PayPal orders. * * @param \WC_Order $wc_order The WooCommerce order. * * @return bool */ public function process( \WC_Order $wc_order ): bool { // phpcs:ignore WordPress.Security.NonceVerification $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) ?: wc_clean( wp_unslash( $_POST['paypal_order_id'] ?? '' ) ); $order = $this->session_handler->order(); if ( ! $order && is_string( $order_id ) && $order_id ) { $order = $this->order_endpoint->order( $order_id ); } if ( ! $order ) { $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ); if ( ! $order_id ) { $this->logger->warning( sprintf( 'No PayPal order ID found in order #%d meta.', $wc_order->get_id() ) ); $this->last_error = __( 'Could not retrieve order. Maybe it was already completed or this browser is not supported. Please check your email or try again with a different browser.', 'woocommerce-paypal-payments' ); return false; } try { $order = $this->order_endpoint->order( $order_id ); } catch ( RuntimeException $exception ) { $this->last_error = __( 'Could not retrieve PayPal order.', 'woocommerce-paypal-payments' ); return false; } } $this->add_paypal_meta( $wc_order, $order, $this->environment ); $error_message = null; if ( $this->order_helper->contains_physical_goods( $order ) && ! $this->order_is_ready_for_process( $order ) ) { $error_message = __( 'The payment is not ready for processing yet.', 'woocommerce-paypal-payments' ); } if ( $error_message ) { $this->last_error = sprintf( // translators: %s is the message of the error. __( 'Payment error: %s', 'woocommerce-paypal-payments' ), $error_message ); return false; } $order = $this->patch_order( $wc_order, $order ); if ( $order->intent() === 'CAPTURE' ) { $order = $this->order_endpoint->capture( $order ); } if ( $order->intent() === 'AUTHORIZE' ) { $order = $this->order_endpoint->authorize( $order ); $wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'false' ); if ( $this->subscription_helper->has_subscription( $wc_order->get_id() ) ) { $wc_order->update_meta_data( '_ppcp_captured_vault_webhook', 'false' ); } } $transaction_id = $this->get_paypal_order_transaction_id( $order ); if ( $transaction_id ) { $this->update_transaction_id( $transaction_id, $wc_order ); } $this->handle_new_order_status( $order, $wc_order ); if ( $this->capture_authorized_downloads( $order ) ) { $this->authorized_payments_processor->capture_authorized_payment( $wc_order ); } $this->last_error = ''; return true; } /** * Returns if an order should be captured immediately. * * @param Order $order The PayPal order. * * @return bool */ private function capture_authorized_downloads( Order $order ): bool { if ( ! $this->settings->has( 'capture_for_virtual_only' ) || ! $this->settings->get( 'capture_for_virtual_only' ) ) { return false; } if ( $order->intent() === 'CAPTURE' ) { return false; } /** * We fetch the order again as the authorize endpoint (from which the Order derives) * drops the item's category, making it impossible to check, if purchase units contain * physical goods. */ $order = $this->order_endpoint->order( $order->id() ); foreach ( $order->purchase_units() as $unit ) { if ( $unit->contains_physical_goods() ) { return false; } } return true; } /** * Returns the last error. * * @return string */ public function last_error(): string { return $this->last_error; } /** * Patches a given PayPal order with a WooCommerce order. * * @param \WC_Order $wc_order The WooCommerce order. * @param Order $order The PayPal order. * * @return Order */ public function patch_order( \WC_Order $wc_order, Order $order ): Order { $this->apply_outbound_order_filters( $wc_order ); $updated_order = $this->order_factory->from_wc_order( $wc_order, $order ); $this->restore_order_from_filters( $wc_order ); $order = $this->order_endpoint->patch_order_with( $order, $updated_order ); return $order; } /** * Whether a given order is ready for processing. * * @param Order $order The order. * * @return bool */ private function order_is_ready_for_process( Order $order ): bool { if ( $order->status()->is( OrderStatus::APPROVED ) || $order->status()->is( OrderStatus::CREATED ) ) { return true; } if ( ! $order->payment_source() || ! $order->payment_source()->card() ) { return false; } return in_array( $this->threed_secure->proceed_with_order( $order ), array( ThreeDSecure::NO_DECISION, ThreeDSecure::PROCCEED, ), true ); } /** * Applies filters to the WC_Order, so they are reflected only on PayPal Order. * * @param WC_Order $wc_order The WoocOmmerce Order. * @return void */ private function apply_outbound_order_filters( WC_Order $wc_order ): void { $items = $wc_order->get_items(); $this->restore_order_data['names'] = array(); foreach ( $items as $item ) { if ( ! $item instanceof \WC_Order_Item ) { continue; } $original_name = $item->get_name(); $new_name = apply_filters( 'woocommerce_paypal_payments_order_line_item_name', $original_name, $item->get_id(), $wc_order->get_id() ); if ( $new_name !== $original_name ) { $this->restore_order_data['names'][ $item->get_id() ] = $original_name; $item->set_name( $new_name ); } } } /** * Restores the WC_Order to it's state before filters. * * @param WC_Order $wc_order The WooCommerce Order. * @return void */ private function restore_order_from_filters( WC_Order $wc_order ): void { if ( is_array( $this->restore_order_data['names'] ?? null ) ) { foreach ( $this->restore_order_data['names'] as $wc_item_id => $original_name ) { $wc_item = $wc_order->get_item( $wc_item_id, false ); if ( $wc_item ) { $wc_item->set_name( $original_name ); } } } } }