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->sandbox_mode = $sandbox_mode; $this->logger = $logger; } /** * 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 { $order = $this->session_handler->order(); if ( ! $order ) { return false; } $wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() ); $wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() ); $wc_order->update_meta_data( PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, $this->sandbox_mode ? 'sandbox' : 'live' ); $error_message = null; if ( ! $this->order_is_approved( $order ) ) { $error_message = __( 'The payment has not been approved 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( PayPalGateway::CAPTURED_META_KEY, 'false' ); } $transaction_id = $this->get_paypal_order_transaction_id( $order ); if ( '' !== $transaction_id ) { $this->set_order_transaction_id( $transaction_id, $wc_order ); } $wc_order->update_status( 'on-hold', __( 'Awaiting payment.', 'woocommerce-paypal-payments' ) ); if ( $order->status()->is( OrderStatus::COMPLETED ) && $order->intent() === 'CAPTURE' ) { $wc_order->update_status( 'processing', __( 'Payment received.', 'woocommerce-paypal-payments' ) ); } if ( $this->capture_authorized_downloads( $order ) && $this->authorized_payments_processor->process( $wc_order ) ) { $wc_order->add_order_note( __( 'Payment successfully captured.', 'woocommerce-paypal-payments' ) ); $wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'true' ); $wc_order->update_status( 'processing' ); } WC()->cart->empty_cart(); $this->session_handler->destroy_session_data(); $this->last_error = ''; return true; } /** * Set transaction id to WC order meta data. * * @param string $transaction_id Transaction id to set. * @param \WC_Order $wc_order Order to set transaction ID to. */ public function set_order_transaction_id( string $transaction_id, \WC_Order $wc_order ) { try { $wc_order->set_transaction_id( $transaction_id ); } catch ( \WC_Data_Exception $exception ) { $this->logger->log( 'warning', sprintf( 'Failed to set transaction ID. Exception caught when tried: %1$s', $exception->getMessage() ) ); } } /** * Retrieve transaction id from PayPal order. * * @param Order $order Order to get transaction id from. * * @return string */ private function get_paypal_order_transaction_id( Order $order ): string { $purchase_units = $order->purchase_units(); if ( ! isset( $purchase_units[0] ) ) { return ''; } $payments = $purchase_units[0]->payments(); if ( null === $payments ) { return ''; } $captures = $payments->captures(); if ( isset( $captures[0] ) ) { return $captures[0]->id(); } return ''; } /** * 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 { $updated_order = $this->order_factory->from_wc_order( $wc_order, $order ); $order = $this->order_endpoint->patch_order_with( $order, $updated_order ); return $order; } /** * Whether a given order is approved. * * @param Order $order The order. * * @return bool */ private function order_is_approved( Order $order ): bool { if ( $order->status()->is( OrderStatus::APPROVED ) ) { return true; } if ( ! $order->payment_source() || ! $order->payment_source()->card() ) { return false; } $is_approved = in_array( $this->threed_secure->proceed_with_order( $order ), array( ThreeDSecure::NO_DECISION, ThreeDSecure::PROCCEED, ), true ); return $is_approved; } }