diff --git a/modules/ppcp-api-client/src/Factory/AmountFactory.php b/modules/ppcp-api-client/src/Factory/AmountFactory.php index 71f1c1a9d..51a6d8c11 100644 --- a/modules/ppcp-api-client/src/Factory/AmountFactory.php +++ b/modules/ppcp-api-client/src/Factory/AmountFactory.php @@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Item; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -132,7 +133,7 @@ class AmountFactory { $total_value = (float) $order->get_total(); if ( ( - CreditCardGateway::ID === $order->get_payment_method() + in_array( $order->get_payment_method(), array( CreditCardGateway::ID, CardButtonGateway::ID ), true ) || ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) ) && $this->is_free_trial_order( $order ) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index ed8c400cf..b64b5e9d8 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -18,7 +18,9 @@ import {hide, setVisible} from "./modules/Helper/Hiding"; import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler"; -const buttonsSpinner = new Spinner('.ppc-button-wrapper'); +// TODO: could be a good idea to have a separate spinner for each gateway, +// but I think we care mainly about the script loading, so one spinner should be enough. +const buttonsSpinner = new Spinner(document.querySelector('.ppc-button-wrapper')); const cardsSpinner = new Spinner('#ppcp-hosted-fields'); const bootstrap = () => { @@ -138,6 +140,11 @@ document.addEventListener( return; } + const paypalButtonGatewayIds = [ + PaymentMethods.PAYPAL, + ...Object.entries(PayPalCommerceGateway.separate_buttons).map(([k, data]) => data.id), + ] + // Sometimes PayPal script takes long time to load, // so we additionally hide the standard order button here to avoid failed orders. // Normally it is hidden later after the script load. @@ -153,12 +160,12 @@ document.addEventListener( } const currentPaymentMethod = getCurrentPaymentMethod(); - const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; + const isPaypalButton = paypalButtonGatewayIds.includes(currentPaymentMethod); const isCards = currentPaymentMethod === PaymentMethods.CARDS; - setVisible(ORDER_BUTTON_SELECTOR, !isPaypal && !isCards, true); + setVisible(ORDER_BUTTON_SELECTOR, !isPaypalButton && !isCards, true); - if (isPaypal) { + if (isPaypalButton) { // stopped after the first rendering of the buttons, in onInit buttonsSpinner.block(); } else { diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index a2186970f..9d008d4b6 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -82,16 +82,27 @@ class CheckoutBootstap { const currentPaymentMethod = getCurrentPaymentMethod(); const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isCard = currentPaymentMethod === PaymentMethods.CARDS; + const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod); const isSavedCard = isCard && isSavedCardSelected(); - const isNotOurGateway = !isPaypal && !isCard; + const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; + const paypalButtonWrappers = { + ...Object.entries(PayPalCommerceGateway.separate_buttons) + .reduce((result, [k, data]) => { + return {...result, [data.id]: data.wrapper} + }, {}), + }; + setVisible(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, true); setVisible('.ppcp-vaulted-paypal-details', isPaypal); setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial); setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); + for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) { + setVisible(wrapper, gatewayId === currentPaymentMethod); + } if (isPaypal && !isFreeTrial) { this.messages.render(); diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 196ad596b..eceb07509 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -1,6 +1,7 @@ export const PaymentMethods = { PAYPAL: 'ppcp-gateway', CARDS: 'ppcp-credit-card-gateway', + CARD_BUTTON: 'ppcp-card-button-gateway', }; export const ORDER_BUTTON_SELECTOR = '#place_order'; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 6ef6a3b2f..2ceee9a0a 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -13,6 +13,16 @@ class Renderer { this.renderButtons(settings.button.wrapper, settings.button.style, contextConfig); this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig); + for (const [fundingSource, data] of Object.entries(settings.separate_buttons)) { + this.renderButtons( + data.wrapper, + data.style, + { + ...contextConfig, + fundingSource: fundingSource, + } + ); + } } renderButtons(wrapper, style, contextConfig) { @@ -20,12 +30,17 @@ class Renderer { return; } - paypal.Buttons({ + const btn = paypal.Buttons({ style, ...contextConfig, onClick: this.onSmartButtonClick, onInit: this.onSmartButtonsInit, - }).render(wrapper); + }); + if (!btn.isEligible()) { + return; + } + + btn.render(wrapper); } isAlreadyRendered(wrapper) { diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index ae5428906..c581a26a8 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -28,7 +28,9 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -421,15 +423,20 @@ class SmartButton implements SmartButtonInterface { ) { add_action( $this->single_product_renderer_hook(), - array( - $this, - 'button_renderer', - ), + function () { + $this->button_renderer( PayPalGateway::ID ); + }, 31 ); } - add_action( $this->pay_order_renderer_hook(), array( $this, 'button_renderer' ), 10 ); + add_action( + $this->pay_order_renderer_hook(), + function (): void { + $this->button_renderer( PayPalGateway::ID ); + $this->button_renderer( CardButtonGateway::ID ); + } + ); $not_enabled_on_minicart = $this->settings->has( 'button_mini_cart_enabled' ) && ! $this->settings->get( 'button_mini_cart_enabled' ); @@ -457,7 +464,13 @@ class SmartButton implements SmartButtonInterface { ); } - add_action( $this->checkout_button_renderer_hook(), array( $this, 'button_renderer' ), 10 ); + add_action( + $this->checkout_button_renderer_hook(), + function (): void { + $this->button_renderer( PayPalGateway::ID ); + $this->button_renderer( CardButtonGateway::ID ); + } + ); $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) && ! $this->settings->get( 'button_cart_enabled' ); @@ -468,7 +481,7 @@ class SmartButton implements SmartButtonInterface { return; } - $this->button_renderer(); + $this->button_renderer( PayPalGateway::ID ); }, 20 ); @@ -524,8 +537,10 @@ class SmartButton implements SmartButtonInterface { /** * Renders the HTML for the buttons. + * + * @param string $gateway_id The gateway ID, like 'ppcp-gateway'. */ - public function button_renderer() { + public function button_renderer( string $gateway_id ) { if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { return; @@ -543,13 +558,13 @@ class SmartButton implements SmartButtonInterface { $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); - if ( ! isset( $available_gateways['ppcp-gateway'] ) ) { + if ( ! isset( $available_gateways[ $gateway_id ] ) ) { return; } // The wrapper is needed for the loading spinner, // otherwise jQuery block() prevents buttons rendering. - echo '
'; + echo '
'; } /** @@ -810,7 +825,7 @@ class SmartButton implements SmartButtonInterface { 'bn_codes' => $this->bn_codes(), 'payer' => $this->payerData(), 'button' => array( - 'wrapper' => '#ppc-button', + 'wrapper' => '#ppc-button-' . PayPalGateway::ID, 'mini_cart_wrapper' => '#ppc-button-minicart', 'cancel_wrapper' => '#ppcp-cancel', 'url' => $this->url(), @@ -830,6 +845,16 @@ class SmartButton implements SmartButtonInterface { 'tagline' => $this->style_for_context( 'tagline', $this->context() ), ), ), + 'separate_buttons' => array( + 'card' => array( + 'id' => CardButtonGateway::ID, + 'wrapper' => '#ppc-button-' . CardButtonGateway::ID, + 'style' => array( + 'shape' => $this->style_for_context( 'shape', $this->context() ), + // TODO: color black, white from the gateway settings. + ), + ), + ), 'hosted_fields' => array( 'wrapper' => '#ppcp-hosted-fields', 'labels' => array( @@ -1018,6 +1043,7 @@ class SmartButton implements SmartButtonInterface { if ( $this->load_button_component() ) { $components[] = 'buttons'; + $components[] = 'funding-eligibility'; } if ( $this->messages_apply->for_country() @@ -1112,6 +1138,9 @@ class SmartButton implements SmartButtonInterface { if ( $source && $source->card() ) { return false; // Ignore for DCC. } + if ( 'card' === $this->session_handler->funding_source() ) { + return false; // Ignore for card buttons. + } return true; } diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 6131c0e4a..c0907b4af 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -204,7 +205,7 @@ class CreateOrderEndpoint implements EndpointInterface { // The cart does not have any info about payment method, so we must handle free trial here. if ( ( - CreditCardGateway::ID === $payment_method + in_array( $payment_method, array( CreditCardGateway::ID, CardButtonGateway::ID ), true ) || ( PayPalGateway::ID === $payment_method && 'card' === $funding_source ) ) && $this->is_free_trial_cart() diff --git a/modules/ppcp-session/src/Cancellation/CancelController.php b/modules/ppcp-session/src/Cancellation/CancelController.php index 51d48008c..3c23983f5 100644 --- a/modules/ppcp-session/src/Cancellation/CancelController.php +++ b/modules/ppcp-session/src/Cancellation/CancelController.php @@ -70,6 +70,10 @@ class CancelController { return; // Ignore for DCC. } + if ( 'card' === $this->session_handler->funding_source() ) { + return; // Ignore for card buttons. + } + $url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() ); add_action( 'woocommerce_review_order_after_submit', diff --git a/modules/ppcp-vaulting/src/PaymentTokenChecker.php b/modules/ppcp-vaulting/src/PaymentTokenChecker.php index 604c7cc37..849c7a08f 100644 --- a/modules/ppcp-vaulting/src/PaymentTokenChecker.php +++ b/modules/ppcp-vaulting/src/PaymentTokenChecker.php @@ -16,6 +16,7 @@ use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; @@ -118,7 +119,7 @@ class PaymentTokenChecker { if ( $tokens ) { try { if ( $this->is_free_trial_order( $wc_order ) ) { - if ( CreditCardGateway::ID === $wc_order->get_payment_method() + if ( in_array( $wc_order->get_payment_method(), array( CreditCardGateway::ID, CardButtonGateway::ID ), true ) || ( PayPalGateway::ID === $wc_order->get_payment_method() && 'card' === $wc_order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) ) { $order = $this->order_repository->for_wc_order( $wc_order ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 8e9a73003..a4c7b35a6 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -29,6 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset; use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet; @@ -126,6 +127,20 @@ return array( $payments_endpoint ); }, + 'wcgateway.card-button-gateway' => static function ( ContainerInterface $container ): CardButtonGateway { + return new CardButtonGateway( + $container->get( 'wcgateway.order-processor' ), + $container->get( 'wcgateway.settings' ), + $container->get( 'session.handler' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'onboarding.state' ), + $container->get( 'wcgateway.transaction-url-provider' ), + $container->get( 'subscription.helper' ), + $container->get( 'onboarding.environment' ), + $container->get( 'vaulting.repository.payment-token' ), + $container->get( 'woocommerce.logger.woocommerce' ) + ); + }, 'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways { $session_handler = $container->get( 'session.handler' ); $settings = $container->get( 'wcgateway.settings' ); @@ -143,7 +158,7 @@ return array( } $section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : ''; - return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID ), true ); + return in_array( $section, array( PayPalGateway::ID, CreditCardGateway::ID, WebhooksStatusPage::ID, PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ); }, 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string { diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index 33fbcef6e..0ff7dc8d1 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Checkout; use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use Psr\Container\ContainerInterface; @@ -59,9 +60,10 @@ class DisableGateways { if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) { return $methods; } - if ( $this->disable_both_gateways() ) { + if ( $this->disable_all_gateways() ) { unset( $methods[ PayPalGateway::ID ] ); unset( $methods[ CreditCardGateway::ID ] ); + unset( $methods[ CardButtonGateway::ID ] ); return $methods; } @@ -87,11 +89,11 @@ class DisableGateways { } /** - * Whether both gateways should be disabled or not. + * Whether all gateways should be disabled or not. * * @return bool */ - private function disable_both_gateways() : bool { + private function disable_all_gateways() : bool { if ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ) { return true; } @@ -110,7 +112,8 @@ class DisableGateways { * @return bool */ private function needs_to_disable_gateways(): bool { - return $this->session_handler->order() !== null; + return $this->session_handler->order() !== null && + 'card' !== $this->session_handler->funding_source(); } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php new file mode 100644 index 000000000..daae61dd3 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php @@ -0,0 +1,331 @@ +id = self::ID; + $this->order_processor = $order_processor; + $this->config = $config; + $this->session_handler = $session_handler; + $this->refund_processor = $refund_processor; + $this->state = $state; + $this->transaction_url_provider = $transaction_url_provider; + $this->subscription_helper = $subscription_helper; + $this->environment = $environment; + $this->onboarded = $state->current_state() === State::STATE_ONBOARDED; + $this->payment_token_repository = $payment_token_repository; + $this->logger = $logger; + + if ( $this->onboarded ) { + $this->supports = array( 'refunds' ); + } + if ( + defined( 'PPCP_FLAG_SUBSCRIPTION' ) + && PPCP_FLAG_SUBSCRIPTION + && $this->gateways_enabled() + && $this->vault_setting_enabled() + ) { + $this->supports = array( + 'refunds', + 'products', + 'subscriptions', + 'subscription_cancellation', + 'subscription_suspension', + 'subscription_reactivation', + 'subscription_amount_changes', + 'subscription_date_changes', + 'subscription_payment_method_change', + 'subscription_payment_method_change_customer', + 'subscription_payment_method_change_admin', + 'multiple_subscriptions', + ); + } + + $this->method_title = __( 'PayPal Card Button', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'The separate payment gateway with the card button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); + $this->title = $this->get_option( 'title', __( 'Debit & Credit Cards', 'woocommerce-paypal-payments' ) ); + $this->description = $this->get_option( 'description', '' ); + + $this->init_form_fields(); + $this->init_settings(); + + add_action( + 'woocommerce_update_options_payment_gateways_' . $this->id, + array( + $this, + 'process_admin_options', + ) + ); + } + + /** + * 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', + 'label' => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), + 'default' => 'no', + 'desc_tip' => true, + 'description' => __( 'Enable/Disable the separate payment gateway with the card button.', 'woocommerce-paypal-payments' ), + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->title, + 'desc_tip' => true, + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->description, + 'desc_tip' => true, + 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + ); + } + + /** + * 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.' ) ) + ); + } + + /** + * If customer has chosen change Subscription payment. + */ + if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { + $saved_paypal_payment = filter_input( INPUT_POST, 'saved_paypal_payment', FILTER_SANITIZE_STRING ); + if ( $saved_paypal_payment ) { + update_post_meta( $order_id, 'payment_token_id', $saved_paypal_payment ); + + return $this->handle_payment_success( $wc_order ); + } + } + + /** + * 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 { + if ( ! $this->order_processor->process( $wc_order ) ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + $this->order_processor->last_error() + ) + ); + } + + if ( $this->subscription_helper->has_subscription( $order_id ) ) { + $this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() ); + } + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalApiException $error ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( RuntimeException $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 ); + } +} diff --git a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php index 24faa5201..631a078c7 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php @@ -9,6 +9,7 @@ declare( strict_types=1 ); namespace WooCommerce\PayPalCommerce\WcGateway\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; @@ -58,7 +59,7 @@ class SectionsRenderer { /** * Renders the Sections tab. */ - public function render() { + public function render(): void { if ( ! $this->should_render() ) { return; } @@ -66,6 +67,7 @@ class SectionsRenderer { $sections = array( PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), CreditCardGateway::ID => __( 'PayPal Card Processing', 'woocommerce-paypal-payments' ), + CardButtonGateway::ID => __( 'PayPal Card Button', 'woocommerce-paypal-payments' ), PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ), WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), ); @@ -80,8 +82,8 @@ class SectionsRenderer { foreach ( $sections as $id => $label ) { $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&' . self::KEY . '=' . $id ); - if ( PayUponInvoiceGateway::ID === $id ) { - $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-pay-upon-invoice-gateway' ); + if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ) ) { + $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $id ); } echo '
  • ' . esc_html( $label ) . ' ' . ( end( $array_keys ) === $id ? '' : '|' ) . '
  • '; } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 8bb469a9d..a828e7684 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -284,6 +284,8 @@ class WCGatewayModule implements ModuleInterface { $methods[] = $container->get( 'wcgateway.credit-card-gateway' ); } + $methods[] = $container->get( 'wcgateway.card-button-gateway' ); + if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) { $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); } diff --git a/tests/stubs/WC_Payment_Gateway.php b/tests/stubs/WC_Payment_Gateway.php index 6fbb823c8..bb87ee010 100644 --- a/tests/stubs/WC_Payment_Gateway.php +++ b/tests/stubs/WC_Payment_Gateway.php @@ -4,7 +4,7 @@ declare(strict_types=1); class WC_Payment_Gateway { - protected function get_option(string $key) : string { + public function get_option(string $key, $empty_value = null) { return $key; } @@ -19,4 +19,4 @@ class WC_Payment_Gateway public function process_admin_options() { } -} \ No newline at end of file +}