diff --git a/modules/ppcp-api-client/src/Helper/class-dccapplies.php b/modules/ppcp-api-client/src/Helper/class-dccapplies.php index ddc259646..2984be4a4 100644 --- a/modules/ppcp-api-client/src/Helper/class-dccapplies.php +++ b/modules/ppcp-api-client/src/Helper/class-dccapplies.php @@ -38,115 +38,7 @@ class DccApplies { 'SGD', 'USD', ), - 'BE' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'BG' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'CY' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'CZ' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'DK' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'EE' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'FI' => array( + 'ES' => array( 'AUD', 'CAD', 'CHF', @@ -182,25 +74,7 @@ class DccApplies { 'SGD', 'USD', ), - 'GR' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'HU' => array( + 'GB' => array( 'AUD', 'CAD', 'CHF', @@ -236,258 +110,6 @@ class DccApplies { 'SGD', 'USD', ), - 'LV' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'LI' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'LT' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'LU' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'MT' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'NL' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'NO' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'PL' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'PT' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'RO' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'SK' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'SI' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'ES' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), - 'SE' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), 'US' => array( 'AUD', 'CAD', @@ -496,25 +118,45 @@ class DccApplies { 'JPY', 'USD', ), - 'GB' => array( - 'AUD', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'JPY', - 'NOK', - 'NZD', - 'PLN', - 'SEK', - 'SGD', - 'USD', - ), + ); + /** + * Which countries support which credit cards. Empty credit card arrays mean no restriction on + * currency. Otherwise only the currencies in the array are supported. + * + * @var array + */ + private $country_card_matrix = array( + 'AU' => array( + 'mastercard' => array(), + 'visa' => array(), + ), + 'ES' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'FR' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'GB' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'GBP', 'USD' ), + ), + 'IT' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'EUR' ), + ), + 'US' => array( + 'mastercard' => array(), + 'visa' => array(), + 'amex' => array( 'USD' ), + 'discover' => array( 'USD' ), + ), ); /** @@ -523,8 +165,7 @@ class DccApplies { * @return bool */ public function for_country_currency(): bool { - $region = wc_get_base_location(); - $country = $region['country']; + $country = $this->country(); $currency = get_woocommerce_currency(); if ( ! in_array( $country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { return false; @@ -532,4 +173,67 @@ class DccApplies { $applies = in_array( $currency, $this->allowed_country_currency_matrix[ $country ], true ); return $applies; } + + /** + * Returns credit cards, which can be used. + * + * @return array + */ + public function valid_cards() : array { + $country = $this->country(); + $cards = array(); + if ( ! isset( $this->country_card_matrix[ $country ] ) ) { + return $cards; + } + + $supported_currencies = $this->country_card_matrix[ $country ]; + foreach ( $supported_currencies as $card => $currencies ) { + if ( $this->can_process_card( $card ) ) { + $cards[] = $card; + } + } + if ( in_array( 'amex', $cards, true ) ) { + $cards[] = 'american-express'; + } + if ( in_array( 'mastercard', $cards, true ) ) { + $cards[] = 'master-card'; + } + return $cards; + } + + /** + * Whether a card can be used or not. + * + * @param string $card The card. + * + * @return bool + */ + public function can_process_card( string $card ) : bool { + $country = $this->country(); + if ( ! isset( $this->country_card_matrix[ $country ] ) ) { + return false; + } + if ( ! isset( $this->country_card_matrix[ $country ][ $card ] ) ) { + return false; + } + + /** + * If the supported currencies array is empty, there are no + * restrictions, which currencies are supported by a card. + */ + $supported_currencies = $this->country_card_matrix[ $country ][ $card ]; + $currency = get_woocommerce_currency(); + return empty( $supported_currencies ) || in_array( $currency, $supported_currencies, true ); + } + + /** + * Returns the country code of the shop. + * + * @return string + */ + private function country() : string { + $region = wc_get_base_location(); + $country = $region['country']; + return $country; + } } diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js index 61e9bcb9a..845b25ecc 100644 --- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js +++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js @@ -10,7 +10,11 @@ const onApprove = (context, errorHandler) => { return res.json(); }).then((data)=>{ if (!data.success) { - errorHandler.genericError(); + if (data.data.code === 100) { + errorHandler.message(data.data.message); + } else { + errorHandler.genericError(); + } console.error(data); if (typeof actions.restart !== 'undefined') { return actions.restart(); diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js index ec38441af..bf675b18b 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js @@ -5,6 +5,7 @@ class CreditCardRenderer { constructor(defaultConfig, errorHandler) { this.defaultConfig = defaultConfig; this.errorHandler = errorHandler; + this.cardValid = false; } render(wrapper, contextConfig) { @@ -96,7 +97,7 @@ class CreditCardRenderer { return state.fields[key].isValid; }); - if (formValid) { + if (formValid && this.cardValid) { let vault = document.querySelector(wrapper + ' .ppcp-credit-card-vault') ? document.querySelector(wrapper + ' .ppcp-credit-card-vault').checked : false; @@ -110,12 +111,21 @@ class CreditCardRenderer { return contextConfig.onApprove(payload); }); } else { - this.errorHandler.message(this.defaultConfig.hosted_fields.labels.fields_not_valid); + const message = ! this.cardValid ? this.defaultConfig.hosted_fields.labels.card_not_supported : this.defaultConfig.hosted_fields.labels.fields_not_valid; + this.errorHandler.message(message); } } hostedFields.on('inputSubmitRequest', function () { submitEvent(null); }); + hostedFields.on('cardTypeChange', (event) => { + if ( ! event.cards.length ) { + this.cardValid = false; + return; + } + const validCards = this.defaultConfig.hosted_fields.valid_cards; + this.cardValid = validCards.indexOf(event.cards[0].type) !== -1; + }) document.querySelector(wrapper + ' button').addEventListener( 'click', submitEvent diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php index a4d288577..e5ae5c14a 100644 --- a/modules/ppcp-button/services.php +++ b/modules/ppcp-button/services.php @@ -9,7 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Button; -use Dhii\Data\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Button\Assets\DisabledSmartButton; use WooCommerce\PayPalCommerce\Button\Assets\SmartButton; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; @@ -134,10 +133,19 @@ return array( }, 'button.endpoint.approve-order' => static function ( $container ): ApproveOrderEndpoint { $request_data = $container->get( 'button.request-data' ); - $order_endpoint = $container->get( 'api.endpoint.order' ); + $order_endpoint = $container->get( 'api.endpoint.order' ); $session_handler = $container->get( 'session.handler' ); - $three_d_secure = $container->get( 'button.helper.three-d-secure' ); - return new ApproveOrderEndpoint( $request_data, $order_endpoint, $session_handler, $three_d_secure ); + $three_d_secure = $container->get( 'button.helper.three-d-secure' ); + $settings = $container->get( 'wcgateway.settings' ); + $dcc_applies = $container->get( 'api.helpers.dccapplies' ); + return new ApproveOrderEndpoint( + $request_data, + $order_endpoint, + $session_handler, + $three_d_secure, + $settings, + $dcc_applies + ); }, 'button.endpoint.data-client-id' => static function( $container ) : DataClientIdEndpoint { $request_data = $container->get( 'button.request-data' ); diff --git a/modules/ppcp-button/src/Assets/class-smartbutton.php b/modules/ppcp-button/src/Assets/class-smartbutton.php index 9e6ca8e99..5c858a7be 100644 --- a/modules/ppcp-button/src/Assets/class-smartbutton.php +++ b/modules/ppcp-button/src/Assets/class-smartbutton.php @@ -615,7 +615,12 @@ class SmartButton implements SmartButtonInterface { 'Unfortunatly, your credit card details are not valid.', 'paypal-payments-for-woocommerce' ), + 'card_not_supported' => __( + 'Unfortunatly, we do not support your credit card.', + 'paypal-payments-for-woocommerce' + ), ), + 'valid_cards' => $this->dcc_applies->valid_cards(), ), 'messages' => $this->message_values(), 'labels' => array( @@ -698,9 +703,9 @@ class SmartButton implements SmartButtonInterface { if ( 'GB' === $country ) { $disable_funding[] = 'card'; } - $params['disable-funding'] = implode( ',', array_unique( $disable_funding ) ); - $smart_button_url = add_query_arg( $params, 'https://www.paypal.com/sdk/js' ); + + $smart_button_url = add_query_arg( $params, 'https://www.paypal.com/sdk/js' ); return $smart_button_url; } diff --git a/modules/ppcp-button/src/Endpoint/class-approveorderendpoint.php b/modules/ppcp-button/src/Endpoint/class-approveorderendpoint.php index 7d4eccfaa..783618c43 100644 --- a/modules/ppcp-button/src/Endpoint/class-approveorderendpoint.php +++ b/modules/ppcp-button/src/Endpoint/class-approveorderendpoint.php @@ -13,9 +13,11 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; +use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class ApproveOrderEndpoint @@ -53,6 +55,20 @@ class ApproveOrderEndpoint implements EndpointInterface { */ private $threed_secure; + /** + * The settings. + * + * @var Settings + */ + private $settings; + + /** + * The DCC applies object. + * + * @var DccApplies + */ + private $dcc_applies; + /** * ApproveOrderEndpoint constructor. * @@ -60,18 +76,24 @@ class ApproveOrderEndpoint implements EndpointInterface { * @param OrderEndpoint $order_endpoint The order endpoint. * @param SessionHandler $session_handler The session handler. * @param ThreeDSecure $three_d_secure The 3d secure helper object. + * @param Settings $settings The settings. + * @param DccApplies $dcc_applies The DCC applies object. */ public function __construct( RequestData $request_data, OrderEndpoint $order_endpoint, SessionHandler $session_handler, - ThreeDSecure $three_d_secure + ThreeDSecure $three_d_secure, + Settings $settings, + DccApplies $dcc_applies ) { $this->request_data = $request_data; $this->api_endpoint = $order_endpoint; $this->session_handler = $session_handler; $this->threed_secure = $three_d_secure; + $this->settings = $settings; + $this->dcc_applies = $dcc_applies; } /** @@ -110,6 +132,23 @@ class ApproveOrderEndpoint implements EndpointInterface { } if ( $order->payment_source() && $order->payment_source()->card() ) { + if ( $this->settings->has( 'disable_cards' ) ) { + $disabled_cards = (array) $this->settings->get( 'disable_cards' ); + $card = strtolower( $order->payment_source()->card()->brand() ); + if ( 'master_card' === $card ) { + $card = 'mastercard'; + } + + if ( ! $this->dcc_applies->can_process_card( $card ) || in_array( $card, $disabled_cards, true ) ) { + throw new RuntimeException( + __( + 'Unfortunately, we do not accept this card.', + 'paypal-payments-for-woocommerce' + ), + 100 + ); + } + } $proceed = $this->threed_secure->proceed_with_order( $order ); if ( ThreeDSecure::RETRY === $proceed ) { throw new RuntimeException( diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index a7b01bba6..09b6a7af2 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway; use Dhii\Data\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; +use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn; use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail; @@ -1597,6 +1598,25 @@ return array( if ( 'GB' === $country ) { unset( $fields['disable_funding']['options']['card'] ); } + + $dcc_applies = $container->get( 'api.helpers.dccapplies' ); + /** + * Depending on your store location, some credit cards can't be used. + * Here, we filter them out. + * + * The DCC Applies object. + * + * @var DccApplies $dcc_applies + */ + $card_options = $fields['disable_cards']['options']; + foreach ( $card_options as $card => $label ) { + if ( $dcc_applies->can_process_card( $card ) ) { + continue; + } + unset( $card_options[ $card ] ); + } + $fields['disable_cards']['options'] = $card_options; + $fields['card_icons']['options'] = $card_options; return $fields; }, diff --git a/modules/ppcp-wc-gateway/src/Settings/class-settingsrenderer.php b/modules/ppcp-wc-gateway/src/Settings/class-settingsrenderer.php index ce480a68d..31fa01a43 100644 --- a/modules/ppcp-wc-gateway/src/Settings/class-settingsrenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/class-settingsrenderer.php @@ -427,19 +427,6 @@ class SettingsRenderer { 'paypal-payments-for-woocommerce' ); ?> - - - -