woocommerce/payment-gateway/Blocks/Gateway_Checkout_Block_Integration.php
<?php
/**
* WooCommerce Payment Gateway Framework
*
* This source file is subject to the GNU General Public License v3.0
* that is bundled with this package in the file license.txt.
* It is also available through the world-wide-web at this URL:
* http://www.gnu.org/licenses/gpl-3.0.html
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@skyverge.com so we can send you a copy immediately.
*
* DISCLAIMER
*
* Do not edit or add to this file if you wish to upgrade the plugin to newer
* versions in the future. If you wish to customize the plugin for your
* needs please refer to http://www.skyverge.com
*
* @package SkyVerge/WooCommerce/Payment-Gateway/External_Checkout
* @author SkyVerge
* @copyright Copyright (c) 2013-2024, SkyVerge, Inc.
* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0
*/
namespace SkyVerge\WooCommerce\PluginFramework\v5_12_4\Payment_Gateway\Blocks;
use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;
use Automattic\WooCommerce\StoreApi\Payments\PaymentContext;
use Automattic\WooCommerce\StoreApi\Payments\PaymentResult;
use SkyVerge\WooCommerce\PluginFramework\v5_12_4\SV_WC_Payment_Gateway;
use SkyVerge\WooCommerce\PluginFramework\v5_12_4\SV_WC_Payment_Gateway_Helper;
use SkyVerge\WooCommerce\PluginFramework\v5_12_4\SV_WC_Payment_Gateway_Payment_Form;
use SkyVerge\WooCommerce\PluginFramework\v5_12_4\SV_WC_Payment_Gateway_Payment_Token;
use SkyVerge\WooCommerce\PluginFramework\v5_12_4\SV_WC_Payment_Gateway_Plugin;
use SkyVerge\WooCommerce\PluginFramework\v5_12_4\Blocks\Traits\Block_Integration_Trait;
use WC_HTTPS;
use WC_Subscriptions_Cart;
if ( ! class_exists( '\SkyVerge\WooCommerce\PluginFramework\v5_12_4\Payment_Gateway\Blocks\Gateway_Checkout_Block_Integration' ) ) :
/**
* Base class for handling support for the WooCommerce Checkout block in gateways.
*
* For support in non-gateways, {@see Block_Integration}.
*
* @since 5.12.0
*/
#[\AllowDynamicProperties]
abstract class Gateway_Checkout_Block_Integration extends AbstractPaymentMethodType {
use Block_Integration_Trait;
/** @var SV_WC_Payment_Gateway_Plugin instance of the current plugin */
protected SV_WC_Payment_Gateway_Plugin $plugin;
/** @var SV_WC_Payment_Gateway gateway handling integration */
protected SV_WC_Payment_Gateway $gateway;
/** @var string supported block name */
protected string $block_name = 'checkout';
/**
* Block integration constructor.
*
* @since 5.12.0
*
* @param SV_WC_Payment_Gateway_Plugin $plugin
* @param SV_WC_Payment_Gateway $gateway
*/
public function __construct( SV_WC_Payment_Gateway_Plugin $plugin, SV_WC_Payment_Gateway $gateway ) {
$this->plugin = $plugin;
$this->gateway = $gateway;
$this->settings = $gateway->settings;
$this->add_hooks();
}
/**
* Adds hooks.
*
* @since 5.12.0
*
* @return void
*/
protected function add_hooks(): void {
// extends the block payment data for the current gateway
add_filter( 'wc_' . $this->get_name() . '_' . $this->block_name . '_block_payment_method_data', [ $this, 'add_payment_method_data' ], 10, 2 );
// prepares payment data for processing in the backend
add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'prepare_payment_data' ], 10, 2 );
// AJAX endpoint hooks for front-end logging
$this->add_ajax_logging();
}
/**
* Determines if the payment method is available in the checkout block context.
*
* @since 5.12.0
*
* @return bool
*/
public function is_active() : bool {
return $this->gateway->is_available();
}
/**
* Determines if AJAX logging is enabled for the gateway.
*
* @since 5.12.0
*
* @return bool
*/
protected function is_ajax_logging_enabled(): bool {
return ! $this->gateway->debug_off();
}
/**
* Gets the payment method script handles.
*
* Defaults to {@see get_script_handles()} but concrete implementations may override this.
*
* @since 5.12.0
*
* @return string[]
*/
public function get_payment_method_script_handles() : array {
return $this->get_script_handles();
}
/**
* Gets the payment method data.
*
* @since 5.12.0
*
* @return array<string, mixed>
*/
public function get_payment_method_data() : array {
$payment_method_data = [
'id' => $this->gateway->get_id_dasherized(), // dashes
'name' => $this->gateway->get_id(), // underscores
'type' => $this->gateway->get_payment_type(), // typically 'credit-card' or 'echeck', for internal use
'title' => $this->gateway->get_title(), // user-facing display title
'description' => $this->gateway->get_description(), // user-facing description
'icons' => $this->get_gateway_icons(), // icon or card icons displayed next to title
'supported_card_types' => $this->get_supported_card_types(), // card types that the gateway supports, regardless if enabled in settings or not
'enabled_card_types' => $this->get_enabled_card_types(), // card types that are enabled in settings
'defaults' => $this->get_gateway_defaults(), // used to pre-populate payment method fields (typically in test mode)
'placeholders' => $this->get_placeholders(), // used in some payment method fields
'supports' => array_values( $this->gateway->supports ), // list of supported features
'flags' => $this->get_gateway_flags(), // list of gateway configuration flags
'gateway' => $this->get_gateway_configuration(), // other gateway configuration values
'apple_pay' => $this->get_apple_pay_configuration(), // Apple Pay configuration values
'google_pay' => $this->get_google_pay_configuration(), // Google Pay configuration values
'debug_mode' => $this->get_debug_mode(), // the current debug mode (log, checkout, full, off)
'i18n' => $this->gateway->get_gateway_payment_form_localized_params(),
'date_format' => wc_date_format(),
'time_format' => wc_time_format(),
'sample_check' => WC_HTTPS::force_https_url( $this->plugin->get_payment_gateway_framework_assets_url(). '/images/sample-check-sprite.png' ),
'help_tip' => WC_HTTPS::force_https_url( WC()->plugin_url() . '/assets/images/help.png' ),
'ajax_url' => WC_HTTPS::force_https_url( admin_url( 'admin-ajax.php' ) ),
'ajax_log_nonce' => wp_create_nonce( 'wc_' . $this->get_name() . '_' . $this->block_name . '_block_log' ),
];
/**
* Filters gateway-specific payment method data for the Checkout Block.
*
* @since 5.12.0
*
* @param array<string, mixed> $payment_method_data
* @param SV_WC_Payment_Gateway $gateway
*/
return (array) apply_filters( "wc_{$this->gateway->get_id()}_{$this->block_name}_block_payment_method_data", $payment_method_data, $this->gateway );
}
/**
* Gets the payment data as a JSON string.
*
* @since 5.12.0
*
* @return string JSON
*/
public function get_payment_method_data_json() : string {
return wp_json_encode( $this->get_payment_method_data() );
}
/**
* Adds payment method data.
*
* Plugins that extend this class may override this method to add additional payment method data.
* @see Gateway_Checkout_Block_Integration::get_payment_method_data()
*
* @since 5.12.0
*
* @param array $payment_method_data
* @param SV_WC_Payment_Gateway $gateway
* @return array
*/
public function add_payment_method_data( array $payment_method_data, SV_WC_Payment_Gateway $gateway ) : array {
return $payment_method_data;
}
/**
* Gets all the supported card types for this gateway.
*
* Some gateways don't support all card types.
*
* @since 5.12.0
*
* @return string[]
*/
protected function get_supported_card_types() : array {
if ( ! $this->gateway->supports_card_types() ) {
return [];
}
return array_map( [ SV_WC_Payment_Gateway_Helper::class, 'normalize_card_type' ], array_keys( $this->gateway->get_available_card_types() ) );
}
/**
* Gets all the enabled card types for this gateway.
*
* A gateway could support more card types, but the merchant may have disabled some.
*
* @since 5.12.0
*
* @return string[]
*/
protected function get_enabled_card_types() : array {
if ( ! $this->gateway->supports_card_types() ) {
return [];
}
return array_map( [ SV_WC_Payment_Gateway_Helper::class, 'normalize_card_type' ], $this->gateway->get_card_types() );
}
/**
* Gets a list of gateway logos as icon image URLs.
*
* If the gateway has a specific icon, it will return that item only.
* Otherwise, it will return a list of icon URLs for each card type supported by the gateway.
*
* @since 5.12.0
*
* @return array<string, string>
*/
protected function get_gateway_icons() : array {
$icons = [];
if ( $this->gateway->icon ) {
$icons = [ $this->gateway->get_method_title() => $this->gateway->icon ];
} elseif ( $this->gateway->is_echeck_gateway() ) {
$icons = [ __( 'eCheck', 'woocommerce' ) => $this->gateway->get_payment_method_image_url( 'echeck' ) ];
} elseif ( $card_types = $this->get_enabled_card_types() ) {
foreach ( $card_types as $card_type ) {
$card_type = SV_WC_Payment_Gateway_Helper::normalize_card_type( $card_type );
$card_name = SV_WC_Payment_Gateway_Helper::payment_type_to_name( $card_type );
if ( $url = $this->gateway->get_payment_method_image_url( $card_type ) ) {
$icons[ $card_name ] = WC_HTTPS::force_https_url( $url );
}
}
}
/**
* Filters the payment gateway icons for the Checkout block.
*
* If the gateway specifies an icon or is an eCheck type, it will return that item only.
* If the gateway doesn't, but supports card types, it will return a list of icon URLs for each card type supported by the gateway.
*
* @since 5.12.0
*
* @param array<string, string> $icons list of icon URLs keyed by payment method or card name
* @param SV_WC_Payment_Gateway $gateway
*/
return apply_filters( "wc_{$this->gateway->get_id()}_{$this->block_name}_block_payment_method_icons", $icons, $this->gateway );
}
/**
* Gets the payment method fields placeholder.
*
* @since 5.12.0
*
* @return array<string, mixed>
*/
protected function get_placeholders() : array {
$placeholders = [
'credit_card_number' => '•••• •••• •••• ••••',
'credit_card_expiry' => 'MM/YY',
'credit_card_csc' => '•••',
'bank_routing_number' => '•••••••••',
];
/**
* Filters the payment gateway placeholders for the Checkout block.
*
* @since 5.12.0
*
* @param array<string, mixed> $placeholders
* @param SV_WC_Payment_Gateway $gateway
*/
return (array) apply_filters( "wc_{$this->gateway->get_id()}_{$this->block_name}_block_payment_method_placeholders", $placeholders, $this->gateway );
}
/**
* Gets the gateway defaults.
*
* @since 5.12.0
*
* @return array<string, mixed>
*/
protected function get_gateway_defaults() : array {
if ( ! $this->gateway->supports_payment_form() ) {
return [];
}
$defaults = [];
// this is needed because some keys may use dashes instead of underscores, which could cause trouble when parsed as JS objects
foreach ( $this->gateway->get_payment_method_defaults() as $default_key => $default_value ) {
$defaults[ str_replace( '-', '_', $default_key ) ] = $default_value;
}
return $defaults;
}
/**
* Returns any gateway flags from configuration (boolean values only).
*
* @since 5.12.0
*
* @return array<string, bool>
*/
protected function get_gateway_flags() : array {
$flags = [
'settings_inherited' => $this->gateway->inherit_settings(),
'is_test_environment' => $this->gateway->is_test_environment(),
'is_credit_card_gateway' => $this->gateway->is_credit_card_gateway(),
'is_echeck_gateway' => $this->gateway->is_echeck_gateway(),
'csc_enabled' => $this->gateway->csc_enabled(),
'csc_enabled_for_tokens' => $this->gateway->csc_enabled_for_tokens(),
'tokenization_enabled' => $this->gateway->supports_tokenization() && $this->gateway->tokenization_enabled(),
'detailed_decline_messages_enabled' => $this->gateway->is_detailed_customer_decline_messages_enabled(),
'logging_enabled' => 'off' !== $this->get_debug_mode(),
'has_subscription' => false,
'has_subscription_renewal' => false,
];
$force_tokenization = false;
if ( $this->plugin->is_subscriptions_active() && class_exists( 'WC_Subscriptions_Cart' ) ) {
$cart_has_subscription = $force_tokenization = WC_Subscriptions_Cart::cart_contains_subscription();
$flags['has_subscription'] = $cart_has_subscription;
$flags['has_subscription_renewal'] = $cart_has_subscription && isset( WC()->cart->recurring_carts ) && ! empty( WC()->cart->recurring_carts );
}
$flags['tokenization_forced'] = $force_tokenization;
return $flags;
}
/**
* Gets any gateway configuration values.
*
* @since 5.12.0
*
* @return array<string, mixed>
*/
protected function get_gateway_configuration() : array {
return [
'transaction_type' => $this->gateway->supports_credit_card_authorization() & $this->gateway->supports_credit_card_charge() ? $this->gateway->get_option( 'transaction_type', 'charge' ) : null,
'charge_virtual_orders' => $this->gateway->supports_credit_card_charge_virtual() ? 'yes' === $this->gateway->get_option( 'charge_virtual', 'no' ) : null,
'enable_partial_capture' => $this->gateway->supports_credit_card_partial_capture() ? 'yes' === $this->gateway->get_option( 'enable_partial_capture', 'no' ) : null,
'enable_paid_capture' => $this->gateway->supports_credit_card_capture() ? 'yes' === $this->gateway->get_option( 'enable_paid_capture', 'no' ) : null,
];
}
/**
* Gets the Apple Pay configuration for the current gateway, if available.
*
* @since 5.12.0
*
* @return array<string, mixed>|null returns null if Apple Pay is unsupported by the gateway
*/
protected function get_apple_pay_configuration() : ?array {
$apple_pay_configuration = null;
if ( $this->gateway->supports_apple_pay() ) {
$apple_pay = $this->plugin->get_apple_pay_instance();
$processing_gateway = $apple_pay ? $apple_pay->get_processing_gateway() : null;
if ( $processing_gateway && $this->gateway->get_id() === $processing_gateway->get_id() ) {
$apple_pay_configuration = [
'merchant_id' => $apple_pay->get_merchant_id(),
'merchant_name' => get_bloginfo( 'name' ),
'validate_nonce' => wp_create_nonce( 'wc_' . $this->gateway->get_id() . '_apple_pay_validate_merchant' ),
'recalculate_totals_nonce' => wp_create_nonce( 'wc_' . $this->gateway->get_id() . '_apple_pay_recalculate_totals' ),
'process_nonce' => wp_create_nonce( 'wc_' . $this->gateway->get_id() . '_apple_pay_process_payment' ),
'button_style' => $apple_pay->get_button_style(),
'card_types' => $apple_pay->get_supported_networks(),
'countries' => $this->gateway->get_available_countries(),
'currencies' => $this->gateway->get_apple_pay_currencies(),
'capabilities' => $this->gateway->get_apple_pay_capabilities(),
'flags' => [
'is_enabled' => $apple_pay->is_enabled(),
'is_available' => $apple_pay->is_available() && $apple_pay->supports_checkout_block(),
'is_test_environment' => $apple_pay->is_test_mode(),
],
];
}
}
return $apple_pay_configuration;
}
/**
* Gets the Google Pay configuration for the current gateway, if available.
*
* @since 5.12.0
*
* @return array<string, mixed>|null returns null if Google Pay is unsupported by the gateway
*/
protected function get_google_pay_configuration() : ?array {
$google_pay_configuration = null;
if ( $this->gateway->supports_google_pay() ) {
$google_pay = $this->plugin->get_google_pay_instance();
$processing_gateway = $google_pay ? $google_pay->get_processing_gateway() : null;
if ( $processing_gateway && $this->gateway->get_id() === $processing_gateway->get_id() ) {
$google_pay_configuration = [
'merchant_id' => $google_pay->get_merchant_id(),
'merchant_name' => get_bloginfo( 'name' ),
'recalculate_totals_nonce' => wp_create_nonce( 'wc_' . $this->gateway->get_id() . '_google_pay_recalculate_totals' ),
'process_nonce' => wp_create_nonce( 'wc_' . $this->gateway->get_id() . '_google_pay_process_payment' ),
'button_style' => $google_pay->get_button_style(),
'card_types' => $google_pay->get_supported_networks(),
'countries' => $google_pay->get_available_countries(),
'currencies' => [ get_woocommerce_currency() ],
'flags' => [
'is_enabled' => $google_pay->is_enabled(),
'is_available' => $google_pay->is_available() && $google_pay->supports_checkout_block(),
'is_test_environment' => $google_pay->is_test_mode(),
],
];
}
}
return $google_pay_configuration;
}
/**
* Gets the debug mode configured for the gateway.
*
* - `off`: no logging
* - `log`: save to log file
* - `checkout`: save to log file and display in checkout page
* - `full`: save to log file and display in checkout page
*
* @since 5.12.0
*
* @return string one of off, log, checkout or full
*/
protected function get_debug_mode() : string {
$debug_mode = $this->gateway->get_debug_mode();
return 'both' === $debug_mode ? 'full' : $debug_mode;
}
/**
* Prepare payment data for processing by the gateway.
*
* This method does not actually process the payment - it simply adjusts the payment data to support legacy processing.
*
* The checkout block has built-in support for tokenization & payment tokens, but it sends the data from the frontend
* with field names and values that our existing payment processing does not expect.
*
* For example, it sends the internal (core) token ID instead of the gateway-specific token ID. This method fetches
* the token based on core ID and injects the gateway-specific token ID into the `PaymentContext::$payment_data`
* array so that the gateway can process the payment.
*
* @see PaymentContext::$payment_data is converted to `$_POST` by WC core when handling legacy payments.
* @see \Automattic\WooCommerce\StoreApi\Legacy::process_legacy_payment()
* @see SV_WC_Payment_Gateway::get_processing_context()
*
* @internal
*
* @since 5.12.0
*
* @param PaymentContext $payment_context
* @param PaymentResult $payment_result
* @return PaymentResult
*/
public function prepare_payment_data( PaymentContext $payment_context, PaymentResult $payment_result ) : PaymentResult {
/**
* This is a flag to indicate we are processing a block-based checkout, helpful in some gateways that need different handling
* between blocks checkout and legacy shortcode checkout - this will be accessed in $_POST data along with the other field data.
* @see SV_WC_Payment_Gateway_Payment_Form::get_payment_fields()
*/
$additional_payment_data[ 'wc-' . $this->gateway->get_id_dasherized() . '-context' ] = $this->gateway::PROCESSING_CONTEXT_BLOCK;
/**
* Fetch the provider-based token ID for the core token ID:
* @see SV_WC_Payment_Gateway_Direct::get_order()
*/
if ( $token = $this->get_payment_token_for_context( $payment_context ) ) {
$additional_payment_data[ 'wc-' . $this->gateway->get_id_dasherized() . '-payment-token' ] = $token->get_id();
}
/**
* Convert the tokenization flag to the expected key-value pair:
* @see SV_WC_Payment_Gateway_Payment_Tokens_Handler::should_tokenize()
*/
if ( $should_tokenize = $payment_context->payment_data['wc-' . $this->gateway->get_id() . '-new-payment-method'] ) {
$additional_payment_data[ 'wc-' . $this->gateway->get_id_dasherized() . '-tokenize-payment-method' ] = $should_tokenize;
}
/**
* Taking advantage of the fact that objects are passed 'by reference' (actually handles) in PHP:
* @link https://dev.to/nicolus/are-php-objects-passed-by-reference--2gp3
*/
$payment_context->set_payment_data(
array_merge(
$payment_context->payment_data,
$additional_payment_data
)
);
// return the original payment result
return $payment_result;
}
/**
* Gets a payment token for a given payment context.
*
* @since 5.12.0
*
* @param PaymentContext $payment_context
* @return SV_WC_Payment_Gateway_Payment_Token|null
*/
protected function get_payment_token_for_context( PaymentContext $payment_context ): ?SV_WC_Payment_Gateway_Payment_Token {
$core_token_id = $payment_context->payment_data['token'] ?: null;
if ( ! $core_token_id || $payment_context->payment_method !== $this->gateway->get_id() ) {
return null;
}
return $this->gateway->get_payment_tokens_handler()->get_token_by_core_id( get_current_user_id(), $core_token_id );
}
}
endif;