Add card button gateway

This commit is contained in:
Alex P 2022-07-19 09:20:26 +03:00
parent 0ff11054f8
commit d4e8bd453c
15 changed files with 454 additions and 31 deletions

View file

@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@ -132,7 +133,7 @@ class AmountFactory {
$total_value = (float) $order->get_total(); $total_value = (float) $order->get_total();
if ( ( 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 ) ) || ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) )
) )
&& $this->is_free_trial_order( $order ) && $this->is_free_trial_order( $order )

View file

@ -18,7 +18,9 @@ import {hide, setVisible} from "./modules/Helper/Hiding";
import {isChangePaymentPage} from "./modules/Helper/Subscriptions"; import {isChangePaymentPage} from "./modules/Helper/Subscriptions";
import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler"; 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 cardsSpinner = new Spinner('#ppcp-hosted-fields');
const bootstrap = () => { const bootstrap = () => {
@ -138,6 +140,11 @@ document.addEventListener(
return; return;
} }
const paypalButtonGatewayIds = [
PaymentMethods.PAYPAL,
...Object.entries(PayPalCommerceGateway.separate_buttons).map(([k, data]) => data.id),
]
// Sometimes PayPal script takes long time to load, // Sometimes PayPal script takes long time to load,
// so we additionally hide the standard order button here to avoid failed orders. // so we additionally hide the standard order button here to avoid failed orders.
// Normally it is hidden later after the script load. // Normally it is hidden later after the script load.
@ -153,12 +160,12 @@ document.addEventListener(
} }
const currentPaymentMethod = getCurrentPaymentMethod(); const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isPaypalButton = paypalButtonGatewayIds.includes(currentPaymentMethod);
const isCards = currentPaymentMethod === PaymentMethods.CARDS; 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 // stopped after the first rendering of the buttons, in onInit
buttonsSpinner.block(); buttonsSpinner.block();
} else { } else {

View file

@ -82,16 +82,27 @@ class CheckoutBootstap {
const currentPaymentMethod = getCurrentPaymentMethod(); const currentPaymentMethod = getCurrentPaymentMethod();
const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL;
const isCard = currentPaymentMethod === PaymentMethods.CARDS; const isCard = currentPaymentMethod === PaymentMethods.CARDS;
const isSeparateButtonGateway = [PaymentMethods.CARD_BUTTON].includes(currentPaymentMethod);
const isSavedCard = isCard && isSavedCardSelected(); const isSavedCard = isCard && isSavedCardSelected();
const isNotOurGateway = !isPaypal && !isCard; const isNotOurGateway = !isPaypal && !isCard && !isSeparateButtonGateway;
const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart;
const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; 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(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, true);
setVisible('.ppcp-vaulted-paypal-details', isPaypal); setVisible('.ppcp-vaulted-paypal-details', isPaypal);
setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal)); setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal));
setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial); setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial);
setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard); setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard);
for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) {
setVisible(wrapper, gatewayId === currentPaymentMethod);
}
if (isPaypal && !isFreeTrial) { if (isPaypal && !isFreeTrial) {
this.messages.render(); this.messages.render();

View file

@ -1,6 +1,7 @@
export const PaymentMethods = { export const PaymentMethods = {
PAYPAL: 'ppcp-gateway', PAYPAL: 'ppcp-gateway',
CARDS: 'ppcp-credit-card-gateway', CARDS: 'ppcp-credit-card-gateway',
CARD_BUTTON: 'ppcp-card-button-gateway',
}; };
export const ORDER_BUTTON_SELECTOR = '#place_order'; export const ORDER_BUTTON_SELECTOR = '#place_order';

View file

@ -13,6 +13,16 @@ class Renderer {
this.renderButtons(settings.button.wrapper, settings.button.style, contextConfig); this.renderButtons(settings.button.wrapper, settings.button.style, contextConfig);
this.creditCardRenderer.render(settings.hosted_fields.wrapper, 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) { renderButtons(wrapper, style, contextConfig) {
@ -20,12 +30,17 @@ class Renderer {
return; return;
} }
paypal.Buttons({ const btn = paypal.Buttons({
style, style,
...contextConfig, ...contextConfig,
onClick: this.onSmartButtonClick, onClick: this.onSmartButtonClick,
onInit: this.onSmartButtonsInit, onInit: this.onSmartButtonsInit,
}).render(wrapper); });
if (!btn.isEligible()) {
return;
}
btn.render(wrapper);
} }
isAlreadyRendered(wrapper) { isAlreadyRendered(wrapper) {

View file

@ -28,7 +28,9 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@ -421,15 +423,20 @@ class SmartButton implements SmartButtonInterface {
) { ) {
add_action( add_action(
$this->single_product_renderer_hook(), $this->single_product_renderer_hook(),
array( function () {
$this, $this->button_renderer( PayPalGateway::ID );
'button_renderer', },
),
31 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' ) && $not_enabled_on_minicart = $this->settings->has( 'button_mini_cart_enabled' ) &&
! $this->settings->get( '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' ) && $not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) &&
! $this->settings->get( 'button_cart_enabled' ); ! $this->settings->get( 'button_cart_enabled' );
@ -468,7 +481,7 @@ class SmartButton implements SmartButtonInterface {
return; return;
} }
$this->button_renderer(); $this->button_renderer( PayPalGateway::ID );
}, },
20 20
); );
@ -524,8 +537,10 @@ class SmartButton implements SmartButtonInterface {
/** /**
* Renders the HTML for the buttons. * 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() ) { if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
return; return;
@ -543,13 +558,13 @@ class SmartButton implements SmartButtonInterface {
$available_gateways = WC()->payment_gateways->get_available_payment_gateways(); $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
if ( ! isset( $available_gateways['ppcp-gateway'] ) ) { if ( ! isset( $available_gateways[ $gateway_id ] ) ) {
return; return;
} }
// The wrapper is needed for the loading spinner, // The wrapper is needed for the loading spinner,
// otherwise jQuery block() prevents buttons rendering. // otherwise jQuery block() prevents buttons rendering.
echo '<div class="ppc-button-wrapper"><div id="ppc-button"></div></div>'; echo '<div class="ppc-button-wrapper"><div id="ppc-button-' . esc_attr( $gateway_id ) . '"></div></div>';
} }
/** /**
@ -810,7 +825,7 @@ class SmartButton implements SmartButtonInterface {
'bn_codes' => $this->bn_codes(), 'bn_codes' => $this->bn_codes(),
'payer' => $this->payerData(), 'payer' => $this->payerData(),
'button' => array( 'button' => array(
'wrapper' => '#ppc-button', 'wrapper' => '#ppc-button-' . PayPalGateway::ID,
'mini_cart_wrapper' => '#ppc-button-minicart', 'mini_cart_wrapper' => '#ppc-button-minicart',
'cancel_wrapper' => '#ppcp-cancel', 'cancel_wrapper' => '#ppcp-cancel',
'url' => $this->url(), 'url' => $this->url(),
@ -830,6 +845,16 @@ class SmartButton implements SmartButtonInterface {
'tagline' => $this->style_for_context( 'tagline', $this->context() ), '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( 'hosted_fields' => array(
'wrapper' => '#ppcp-hosted-fields', 'wrapper' => '#ppcp-hosted-fields',
'labels' => array( 'labels' => array(
@ -1018,6 +1043,7 @@ class SmartButton implements SmartButtonInterface {
if ( $this->load_button_component() ) { if ( $this->load_button_component() ) {
$components[] = 'buttons'; $components[] = 'buttons';
$components[] = 'funding-eligibility';
} }
if ( if (
$this->messages_apply->for_country() $this->messages_apply->for_country()
@ -1112,6 +1138,9 @@ class SmartButton implements SmartButtonInterface {
if ( $source && $source->card() ) { if ( $source && $source->card() ) {
return false; // Ignore for DCC. return false; // Ignore for DCC.
} }
if ( 'card' === $this->session_handler->funding_source() ) {
return false; // Ignore for card buttons.
}
return true; return true;
} }

View file

@ -28,6 +28,7 @@ use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; 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. // The cart does not have any info about payment method, so we must handle free trial here.
if ( ( if ( (
CreditCardGateway::ID === $payment_method in_array( $payment_method, array( CreditCardGateway::ID, CardButtonGateway::ID ), true )
|| ( PayPalGateway::ID === $payment_method && 'card' === $funding_source ) || ( PayPalGateway::ID === $payment_method && 'card' === $funding_source )
) )
&& $this->is_free_trial_cart() && $this->is_free_trial_cart()

View file

@ -70,6 +70,10 @@ class CancelController {
return; // Ignore for DCC. 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() ); $url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() );
add_action( add_action(
'woocommerce_review_order_after_submit', 'woocommerce_review_order_after_submit',

View file

@ -16,6 +16,7 @@ use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository; use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait; use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
@ -118,7 +119,7 @@ class PaymentTokenChecker {
if ( $tokens ) { if ( $tokens ) {
try { try {
if ( $this->is_free_trial_order( $wc_order ) ) { 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 ) ) || ( 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 ); $order = $this->order_repository->for_wc_order( $wc_order );

View file

@ -29,6 +29,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways; use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer; use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\FraudNet;
@ -126,6 +127,20 @@ return array(
$payments_endpoint $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 { 'wcgateway.disabler' => static function ( ContainerInterface $container ): DisableGateways {
$session_handler = $container->get( 'session.handler' ); $session_handler = $container->get( 'session.handler' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
@ -143,7 +158,7 @@ return array(
} }
$section = isset( $_GET['section'] ) ? sanitize_text_field( wp_unslash( $_GET['section'] ) ) : ''; $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 { 'wcgateway.current-ppcp-settings-page-id' => static function ( ContainerInterface $container ): string {

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Checkout; namespace WooCommerce\PayPalCommerce\WcGateway\Checkout;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -59,9 +60,10 @@ class DisableGateways {
if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) { if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) {
return $methods; return $methods;
} }
if ( $this->disable_both_gateways() ) { if ( $this->disable_all_gateways() ) {
unset( $methods[ PayPalGateway::ID ] ); unset( $methods[ PayPalGateway::ID ] );
unset( $methods[ CreditCardGateway::ID ] ); unset( $methods[ CreditCardGateway::ID ] );
unset( $methods[ CardButtonGateway::ID ] );
return $methods; 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 * @return bool
*/ */
private function disable_both_gateways() : bool { private function disable_all_gateways() : bool {
if ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ) { if ( ! $this->settings->has( 'enabled' ) || ! $this->settings->get( 'enabled' ) ) {
return true; return true;
} }
@ -110,7 +112,8 @@ class DisableGateways {
* @return bool * @return bool
*/ */
private function needs_to_disable_gateways(): 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();
} }
/** /**

View file

@ -0,0 +1,331 @@
<?php
/**
* The PayPal Card Button Gateway
*
* @package WooCommerce\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
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\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
use Psr\Container\ContainerInterface;
/**
* Class CardButtonGateway
*/
class CardButtonGateway extends \WC_Payment_Gateway {
use ProcessPaymentTrait, FreeTrialHandlerTrait;
const ID = 'ppcp-card-button-gateway';
/**
* The processor for orders.
*
* @var OrderProcessor
*/
protected $order_processor;
/**
* The settings.
*
* @var ContainerInterface
*/
protected $config;
/**
* The Session Handler.
*
* @var SessionHandler
*/
protected $session_handler;
/**
* The Refund Processor.
*
* @var RefundProcessor
*/
private $refund_processor;
/**
* The state.
*
* @var State
*/
protected $state;
/**
* Service able to provide transaction url for an order.
*
* @var TransactionUrlProvider
*/
protected $transaction_url_provider;
/**
* The subscription helper.
*
* @var SubscriptionHelper
*/
protected $subscription_helper;
/**
* The payment token repository.
*
* @var PaymentTokenRepository
*/
protected $payment_token_repository;
/**
* Whether the plugin is in onboarded state.
*
* @var bool
*/
private $onboarded;
/**
* The environment.
*
* @var Environment
*/
protected $environment;
/**
* The logger.
*
* @var LoggerInterface
*/
private $logger;
/**
* CardButtonGateway constructor.
*
* @param OrderProcessor $order_processor The Order Processor.
* @param ContainerInterface $config The settings.
* @param SessionHandler $session_handler The Session Handler.
* @param RefundProcessor $refund_processor The Refund Processor.
* @param State $state The state.
* @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
OrderProcessor $order_processor,
ContainerInterface $config,
SessionHandler $session_handler,
RefundProcessor $refund_processor,
State $state,
TransactionUrlProvider $transaction_url_provider,
SubscriptionHelper $subscription_helper,
Environment $environment,
PaymentTokenRepository $payment_token_repository,
LoggerInterface $logger
) {
$this->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 );
}
}

View file

@ -9,6 +9,7 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Settings; namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
@ -58,7 +59,7 @@ class SectionsRenderer {
/** /**
* Renders the Sections tab. * Renders the Sections tab.
*/ */
public function render() { public function render(): void {
if ( ! $this->should_render() ) { if ( ! $this->should_render() ) {
return; return;
} }
@ -66,6 +67,7 @@ class SectionsRenderer {
$sections = array( $sections = array(
PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ), PayPalGateway::ID => __( 'PayPal Checkout', 'woocommerce-paypal-payments' ),
CreditCardGateway::ID => __( 'PayPal Card Processing', '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' ), PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ), WebhooksStatusPage::ID => __( 'Webhooks Status', 'woocommerce-paypal-payments' ),
); );
@ -80,8 +82,8 @@ class SectionsRenderer {
foreach ( $sections as $id => $label ) { foreach ( $sections as $id => $label ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&' . self::KEY . '=' . $id ); $url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway&' . self::KEY . '=' . $id );
if ( PayUponInvoiceGateway::ID === $id ) { if ( in_array( $id, array( PayUponInvoiceGateway::ID, CardButtonGateway::ID ), true ) ) {
$url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-pay-upon-invoice-gateway' ); $url = admin_url( 'admin.php?page=wc-settings&tab=checkout&section=' . $id );
} }
echo '<li><a href="' . esc_url( $url ) . '" class="' . ( $this->page_id === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>'; echo '<li><a href="' . esc_url( $url ) . '" class="' . ( $this->page_id === $id ? 'current' : '' ) . '">' . esc_html( $label ) . '</a> ' . ( end( $array_keys ) === $id ? '' : '|' ) . ' </li>';
} }

View file

@ -284,6 +284,8 @@ class WCGatewayModule implements ModuleInterface {
$methods[] = $container->get( 'wcgateway.credit-card-gateway' ); $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' ) ) { if ( 'DE' === $container->get( 'api.shop.country' ) && 'EUR' === $container->get( 'api.shop.currency' ) ) {
$methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' ); $methods[] = $container->get( 'wcgateway.pay-upon-invoice-gateway' );
} }

View file

@ -4,7 +4,7 @@ declare(strict_types=1);
class WC_Payment_Gateway class WC_Payment_Gateway
{ {
protected function get_option(string $key) : string { public function get_option(string $key, $empty_value = null) {
return $key; return $key;
} }
@ -19,4 +19,4 @@ class WC_Payment_Gateway
public function process_admin_options() { public function process_admin_options() {
} }
} }