id = self::ID; $this->settings_renderer = $settings_renderer; $this->funding_source_renderer = $funding_source_renderer; $this->order_processor = $order_processor; $this->config = $config; $this->session_handler = $session_handler; $this->refund_processor = $refund_processor; $this->transaction_url_provider = $transaction_url_provider; $this->subscription_helper = $subscription_helper; $this->page_id = $page_id; $this->environment = $environment; $this->onboarded = $is_connected; $this->payment_token_repository = $payment_token_repository; $this->logger = $logger; $this->api_shop_country = $api_shop_country; $this->paypal_checkout_url_factory = $paypal_checkout_url_factory; $this->order_button_text = $place_order_button_text; $this->order_endpoint = $order_endpoint; $this->payment_tokens_endpoint = $payment_tokens_endpoint; $this->vault_v3_enabled = $vault_v3_enabled; $this->wc_payment_tokens = $wc_payment_tokens; $this->module_url = $module_url; $this->icon = apply_filters( 'woocommerce_paypal_payments_paypal_gateway_icon', esc_url( $this->module_url ) . 'assets/images/paypal.svg' ); $this->admin_settings_enabled = $admin_settings_enabled; $default_support = array( 'products', 'refunds', 'tokenization', 'add_payment_method', ); $this->supports = array_merge( $default_support, apply_filters( 'woocommerce_paypal_payments_paypal_gateway_supports', array() ) ); $this->method_title = $this->define_method_title(); $this->method_description = $this->define_method_description(); $this->title = apply_filters( 'woocommerce_paypal_payments_gateway_title', $this->config->has( 'title' ) ? $this->config->get( 'title' ) : $this->method_title, $this ); $this->description = apply_filters( 'woocommerce_paypal_payments_gateway_description', $this->config->has( 'description' ) ? $this->config->get( 'description' ) : $this->method_description, $this ); $funding_source = $this->session_handler->funding_source(); if ( $funding_source ) { $order = $this->session_handler->order(); if ( $order && ( $order->status()->is( OrderStatus::APPROVED ) || $order->status()->is( OrderStatus::COMPLETED ) ) ) { $this->title = $this->funding_source_renderer->render_name( $funding_source ); $this->description = $this->funding_source_renderer->render_description( $funding_source ); $this->order_button_text = null; } } $this->init_form_fields(); $this->init_settings(); add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, array( $this, 'process_admin_options', ) ); } /** * Return the gateway's title. * * @return string */ public function get_title() { if ( is_admin() ) { // $theorder and other things for retrieving the order or post info are not available // in the constructor, so must do it here. global $theorder; if ( $theorder instanceof WC_Order ) { if ( $theorder->get_payment_method() === self::ID ) { $payment_method_title = $theorder->get_payment_method_title(); if ( $payment_method_title ) { $this->title = $payment_method_title; } } } } return parent::get_title(); } /** * Whether the Gateway needs to be setup. * * @return bool */ public function needs_setup(): bool { return ! $this->onboarded; } /** * Initializes the form fields. */ public function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', 'desc_tip' => true, 'description' => __( 'In order to use PayPal or Advanced Card Processing, you need to enable the Gateway.', 'woocommerce-paypal-payments' ), 'label' => __( 'Enable the PayPal gateway and more features for your store.', 'woocommerce-paypal-payments' ), 'default' => 'no', ), 'ppcp' => array( 'type' => 'ppcp', ), ); $should_show_enabled_checkbox = $this->is_paypal_tab() && ( $this->config->has( 'merchant_email' ) && $this->config->get( 'merchant_email' ) ); if ( ! $should_show_enabled_checkbox ) { unset( $this->form_fields['enabled'] ); } } /** * Defines the method title. If we are on the credit card tab in the settings, we want to change this. * * @return string */ private function define_method_title(): string { if ( $this->is_connection_tab() ) { return __( 'Account Setup', 'woocommerce-paypal-payments' ); } if ( $this->is_credit_card_tab() ) { return __( 'Advanced Card Processing', 'woocommerce-paypal-payments' ); } if ( $this->is_pay_later_tab() ) { return __( 'PayPal Pay Later', 'woocommerce-paypal-payments' ); } if ( $this->is_paypal_tab() ) { return __( 'Standard Payments', 'woocommerce-paypal-payments' ); } if ( $this->is_pui_tab() ) { return __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ); } return __( 'PayPal', 'woocommerce-paypal-payments' ); } /** * Defines the method description. If we are on the credit card tab in the settings, we want to change this. * * @return string */ private function define_method_description(): string { if ( $this->is_connection_tab() ) { return ''; } if ( $this->is_credit_card_tab() ) { return __( 'Accept debit and credit cards, and local payment methods.', 'woocommerce-paypal-payments' ); } if ( $this->is_pay_later_tab() ) { return ''; } if ( is_admin() ) { return __( 'Accept PayPal, Pay Later and alternative payment types.', 'woocommerce-paypal-payments' ); } return __( 'Pay via PayPal.', 'woocommerce-paypal-payments' ); } // phpcs:disable WordPress.Security.NonceVerification.Recommended /** * Determines, whether the current session is on the credit card tab in the admin settings. * * @return bool */ private function is_credit_card_tab() : bool { return is_admin() && CreditCardGateway::ID === $this->page_id; } /** * Whether we are on the PUI tab. * * @return bool */ private function is_pui_tab():bool { if ( 'DE' !== $this->api_shop_country ) { return false; } return is_admin() && PayUponInvoiceGateway::ID === $this->page_id; } /** * Whether we are on the connection tab. * * @return bool true if is connection tab, otherwise false */ protected function is_connection_tab() : bool { return is_admin() && Settings::CONNECTION_TAB_ID === $this->page_id; } /** * Whether we are on the pay-later tab. * * @return bool true if is pay-later tab, otherwise false */ protected function is_pay_later_tab() : bool { return is_admin() && Settings::PAY_LATER_TAB_ID === $this->page_id; } /** * Whether we are on the PayPal settings tab. * * @return bool */ private function is_paypal_tab() : bool { return ! $this->is_credit_card_tab() && is_admin() && self::ID === $this->page_id; } // phpcs:enable WordPress.Security.NonceVerification.Recommended /** * Process payment for a WooCommerce order. * * @param int $order_id The WooCommerce order id. * * @return array */ public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); if ( ! is_a( $wc_order, WC_Order::class ) ) { return $this->handle_payment_failure( null, new GatewayGenericException( new Exception( 'WC order was not found.' ) ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? ( $_POST['funding_source'] ?? '' ) ) ); if ( $funding_source ) { $wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) ); $wc_order->save(); } if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) && ! $this->subscription_helper->paypal_subscription_id() ) { $ppcp_guest_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null; if ( $this->vault_v3_enabled && $ppcp_guest_payment_for_free_trial ) { $customer_id = $ppcp_guest_payment_for_free_trial->customer->id ?? ''; if ( $customer_id ) { update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id ); } if ( isset( $ppcp_guest_payment_for_free_trial->payment_source->paypal ) ) { $email = ''; if ( isset( $ppcp_guest_payment_for_free_trial->payment_source->paypal->email_address ) ) { $email = $ppcp_guest_payment_for_free_trial->payment_source->paypal->email_address; } $this->wc_payment_tokens->create_payment_token_paypal( $wc_order->get_customer_id(), $ppcp_guest_payment_for_free_trial->id, $email ); } WC()->session->set( 'ppcp_guest_payment_for_free_trial', null ); $wc_order->payment_complete(); return $this->handle_payment_success( $wc_order ); } $customer_id = get_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', true ); if ( $customer_id ) { $customer_tokens = $this->payment_tokens_endpoint->payment_tokens_for_customer( $customer_id ); foreach ( $customer_tokens as $token ) { $payment_source_name = $token['payment_source']->name() ?? ''; if ( $payment_source_name === 'paypal' || $payment_source_name === 'venmo' ) { $wc_order->payment_complete(); return $this->handle_payment_success( $wc_order ); } } } $user_id = (int) $wc_order->get_customer_id(); $tokens = $this->payment_token_repository->all_for_user_id( $user_id ); if ( ! array_filter( $tokens, function ( PaymentToken $token ): bool { return isset( $token->source()->paypal ); } ) ) { return $this->handle_payment_failure( $wc_order, new Exception( 'No saved PayPal account.' ) ); } $wc_order->payment_complete(); return $this->handle_payment_success( $wc_order ); } /** * If customer has chosen change Subscription payment. */ if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); if ( $saved_paypal_payment ) { $payment_token = WC_Payment_Tokens::get( $saved_paypal_payment ); if ( $payment_token ) { $wc_order->add_payment_token( $payment_token ); $wc_order->save(); return $this->handle_payment_success( $wc_order ); } wc_add_notice( __( 'Could not change payment.', 'woocommerce-paypal-payments' ), 'error' ); return array( 'result' => 'failure', 'redirect' => wc_get_checkout_url(), 'errorMessage' => __( 'Could not change payment.', 'woocommerce-paypal-payments' ), ); } } /** * If the WC_Order is paid through the approved webhook. */ //phpcs:disable WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { return $this->handle_payment_success( $wc_order ); } //phpcs:enable WordPress.Security.NonceVerification.Recommended try { try { /** * This filter controls if the method 'process()' from OrderProcessor will be called. * So you can implement your own for example on subscriptions * * - true bool controls execution of 'OrderProcessor::process()' * - $this \WC_Payment_Gateway * - $wc_order \WC_Order */ $process = apply_filters( 'woocommerce_paypal_payments_before_order_process', true, $this, $wc_order ); if ( $process ) { $this->order_processor->process( $wc_order ); } do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order ); return $this->handle_payment_success( $wc_order ); } catch ( PayPalOrderMissingException $exc ) { $order = $this->order_processor->create_order( $wc_order ); return array( 'result' => 'success', 'redirect' => ( $this->paypal_checkout_url_factory )( $order->id() ), ); } } catch ( PayPalApiException $error ) { $retry_keys_messages = array( 'INSTRUMENT_DECLINED' => __( 'Instrument declined.', 'woocommerce-paypal-payments' ), 'PAYER_ACTION_REQUIRED' => __( 'Payer action required, possibly overcharge.', 'woocommerce-paypal-payments' ), ); $retry_errors = array_values( array_filter( array_keys( $retry_keys_messages ), function ( string $key ) use ( $error ): bool { return $error->has_detail( $key ); } ) ); if ( $retry_errors ) { $retry_error_key = $retry_errors[0]; $wc_order->update_status( 'failed', $retry_keys_messages[ $retry_error_key ] . ' ' . $error->details()[0]->description ?? '' ); $this->session_handler->increment_insufficient_funding_tries(); if ( $this->session_handler->insufficient_funding_tries() >= 3 ) { return $this->handle_payment_failure( null, new Exception( __( 'Please use a different payment method.', 'woocommerce-paypal-payments' ), $error->getCode(), $error ) ); } return array( 'result' => 'success', 'redirect' => ( $this->paypal_checkout_url_factory )( $this->session_handler->order()->id() ), ); } return $this->handle_payment_failure( $wc_order, new Exception( Messages::generic_payment_error_message() . ' ' . $error->getMessage(), $error->getCode(), $error ) ); } catch ( Exception $error ) { return $this->handle_payment_failure( $wc_order, $error ); } } /** * Process refund. * * If the gateway declares 'refunds' support, this will allow it to refund. * a passed in amount. * * @param int $order_id Order ID. * @param float $amount Refund amount. * @param string $reason Refund reason. * @return boolean True or false based on success, or a WP_Error object. */ public function process_refund( $order_id, $amount = null, $reason = '' ) { $order = wc_get_order( $order_id ); if ( ! is_a( $order, \WC_Order::class ) ) { return false; } return $this->refund_processor->process( $order, (float) $amount, (string) $reason ); } /** * Return transaction url for this gateway and given order. * * @param \WC_Order $order WC order to get transaction url by. * * @return string */ public function get_transaction_url( $order ): string { $this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order ); return parent::get_transaction_url( $order ); } /** * Updates WooCommerce gateway option. * * @param string $key The option key. * @param string $value The option value. * @return bool was anything saved? */ public function update_option( $key, $value = '' ) { $ret = parent::update_option( $key, $value ); if ( 'enabled' === $key ) { $this->config->set( 'enabled', 'yes' === $value ); $this->config->persist(); return true; } return $ret; } /** * Override the parent admin_options method. */ public function admin_options() { if ( ! $this->admin_settings_enabled ) { parent::admin_options(); } do_action( 'woocommerce_paypal_payments_gateway_admin_options_wrapper', $this ); } /** * Returns the settings renderer. * * @return SettingsRenderer */ protected function settings_renderer(): SettingsRenderer { return $this->settings_renderer; } }