validate if credit card can be used in a country

This commit is contained in:
David Remer 2020-09-29 09:59:22 +03:00
parent fed6df2578
commit 82be811271
6 changed files with 298 additions and 11 deletions

View file

@ -38,6 +38,24 @@ class DccApplies {
'SGD', 'SGD',
'USD', 'USD',
), ),
'AT' => array(
'AUD',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'JPY',
'NOK',
'NZD',
'PLN',
'SEK',
'SGD',
'USD',
),
'BE' => array( 'BE' => array(
'AUD', 'AUD',
'CAD', 'CAD',
@ -74,6 +92,24 @@ class DccApplies {
'SGD', 'SGD',
'USD', 'USD',
), ),
'CA' => array(
'AUD',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'JPY',
'NOK',
'NZD',
'PLN',
'SEK',
'SGD',
'USD',
),
'CY' => array( 'CY' => array(
'AUD', 'AUD',
'CAD', 'CAD',
@ -517,14 +553,148 @@ class DccApplies {
); );
/**
* 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(),
),
'AT' => array(
'mastercard' => array(),
'visa' => array(),
),
'BE' => array(
'mastercard' => array(),
'visa' => array(),
),
'CA' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'CAD' ),
'jcb' => array( 'CAD' ),
),
'CY' => array(
'mastercard' => array(),
'visa' => array(),
),
'CZ' => array(
'mastercard' => array(),
'visa' => array(),
),
'DK' => array(
'mastercard' => array(),
'visa' => array(),
),
'EE' => array(
'mastercard' => array(),
'visa' => array(),
),
'FI' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'EUR' ),
),
'FR' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'EUR' ),
),
'GR' => array(
'mastercard' => array(),
'visa' => array(),
),
'HU' => array(
'mastercard' => array(),
'visa' => array(),
),
'IT' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'EUR' ),
),
'LV' => array(
'mastercard' => array(),
'visa' => array(),
),
'LI' => array(
'mastercard' => array(),
'visa' => array(),
),
'LT' => array(
'mastercard' => array(),
'visa' => array(),
),
'LU' => array(
'mastercard' => array(),
'visa' => array(),
),
'MT' => array(
'mastercard' => array(),
'visa' => array(),
),
'NL' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'EUR' ),
),
'NO' => array(
'mastercard' => array(),
'visa' => array(),
),
'PL' => array(
'mastercard' => array(),
'visa' => array(),
),
'PT' => array(
'mastercard' => array(),
'visa' => array(),
),
'RO' => array(
'mastercard' => array(),
'visa' => array(),
),
'SK' => array(
'mastercard' => array(),
'visa' => array(),
),
'SI' => array(
'mastercard' => array(),
'visa' => array(),
),
'ES' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'EUR' ),
),
'SE' => array(
'mastercard' => array(),
'visa' => array(),
),
'US' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'USD' ),
'discover' => array( 'USD' ),
),
'GB' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'GBP', 'USD' ),
),
);
/** /**
* Returns whether DCC can be used in the current country and the current currency used. * Returns whether DCC can be used in the current country and the current currency used.
* *
* @return bool * @return bool
*/ */
public function for_country_currency(): bool { public function for_country_currency(): bool {
$region = wc_get_base_location(); $country = $this->country();
$country = $region['country'];
$currency = get_woocommerce_currency(); $currency = get_woocommerce_currency();
if ( ! in_array( $country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { if ( ! in_array( $country, array_keys( $this->allowed_country_currency_matrix ), true ) ) {
return false; return false;
@ -532,4 +702,67 @@ class DccApplies {
$applies = in_array( $currency, $this->allowed_country_currency_matrix[ $country ], true ); $applies = in_array( $currency, $this->allowed_country_currency_matrix[ $country ], true );
return $applies; 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;
}
} }

View file

@ -5,6 +5,7 @@ class CreditCardRenderer {
constructor(defaultConfig, errorHandler) { constructor(defaultConfig, errorHandler) {
this.defaultConfig = defaultConfig; this.defaultConfig = defaultConfig;
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.cardValid = false;
} }
render(wrapper, contextConfig) { render(wrapper, contextConfig) {
@ -79,7 +80,7 @@ class CreditCardRenderer {
return state.fields[key].isValid; return state.fields[key].isValid;
}); });
if (formValid) { if (formValid && this.cardValid) {
let vault = document.querySelector(wrapper + ' .ppcp-credit-card-vault') ? let vault = document.querySelector(wrapper + ' .ppcp-credit-card-vault') ?
document.querySelector(wrapper + ' .ppcp-credit-card-vault').checked : false; document.querySelector(wrapper + ' .ppcp-credit-card-vault').checked : false;
@ -93,12 +94,21 @@ class CreditCardRenderer {
return contextConfig.onApprove(payload); return contextConfig.onApprove(payload);
}); });
} else { } 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 () { hostedFields.on('inputSubmitRequest', function () {
submitEvent(null); 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( document.querySelector(wrapper + ' button').addEventListener(
'click', 'click',
submitEvent submitEvent

View file

@ -9,7 +9,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button; namespace WooCommerce\PayPalCommerce\Button;
use Dhii\Data\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Button\Assets\DisabledSmartButton; use WooCommerce\PayPalCommerce\Button\Assets\DisabledSmartButton;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton; use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
@ -138,7 +137,15 @@ return array(
$session_handler = $container->get( 'session.handler' ); $session_handler = $container->get( 'session.handler' );
$three_d_secure = $container->get( 'button.helper.three-d-secure' ); $three_d_secure = $container->get( 'button.helper.three-d-secure' );
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
return new ApproveOrderEndpoint( $request_data, $order_endpoint, $session_handler, $three_d_secure, $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 { 'button.endpoint.data-client-id' => static function( $container ) : DataClientIdEndpoint {
$request_data = $container->get( 'button.request-data' ); $request_data = $container->get( 'button.request-data' );

View file

@ -601,8 +601,13 @@ class SmartButton implements SmartButtonInterface {
'Unfortunatly, your credit card details are not valid.', 'Unfortunatly, your credit card details are not valid.',
'paypal-payments-for-woocommerce' '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(), 'messages' => $this->message_values(),
'labels' => array( 'labels' => array(
'error' => array( 'error' => array(

View file

@ -13,6 +13,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
@ -61,6 +62,13 @@ class ApproveOrderEndpoint implements EndpointInterface {
*/ */
private $settings; private $settings;
/**
* The DCC applies object.
*
* @var DccApplies
*/
private $dcc_applies;
/** /**
* ApproveOrderEndpoint constructor. * ApproveOrderEndpoint constructor.
* *
@ -69,13 +77,15 @@ class ApproveOrderEndpoint implements EndpointInterface {
* @param SessionHandler $session_handler The session handler. * @param SessionHandler $session_handler The session handler.
* @param ThreeDSecure $three_d_secure The 3d secure helper object. * @param ThreeDSecure $three_d_secure The 3d secure helper object.
* @param Settings $settings The settings. * @param Settings $settings The settings.
* @param DccApplies $dcc_applies The DCC applies object.
*/ */
public function __construct( public function __construct(
RequestData $request_data, RequestData $request_data,
OrderEndpoint $order_endpoint, OrderEndpoint $order_endpoint,
SessionHandler $session_handler, SessionHandler $session_handler,
ThreeDSecure $three_d_secure, ThreeDSecure $three_d_secure,
Settings $settings Settings $settings,
DccApplies $dcc_applies
) { ) {
$this->request_data = $request_data; $this->request_data = $request_data;
@ -83,6 +93,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
$this->session_handler = $session_handler; $this->session_handler = $session_handler;
$this->threed_secure = $three_d_secure; $this->threed_secure = $three_d_secure;
$this->settings = $settings; $this->settings = $settings;
$this->dcc_applies = $dcc_applies;
} }
/** /**
@ -123,11 +134,12 @@ class ApproveOrderEndpoint implements EndpointInterface {
if ( $order->payment_source() && $order->payment_source()->card() ) { if ( $order->payment_source() && $order->payment_source()->card() ) {
if ( $this->settings->has( 'disable_cards' ) ) { if ( $this->settings->has( 'disable_cards' ) ) {
$disabled_cards = (array) $this->settings->get( 'disable_cards' ); $disabled_cards = (array) $this->settings->get( 'disable_cards' );
if ( in_array( 'mastercard', $disabled_cards, true ) ) {
$disabled_cards[] = 'master_card';
}
$card = strtolower( $order->payment_source()->card()->brand() ); $card = strtolower( $order->payment_source()->card()->brand() );
if ( in_array( $card, $disabled_cards, true ) ) { if ( 'master_card' === $card ) {
$card = 'mastercard';
}
if ( ! $this->dcc_applies->can_process_card( $card ) || in_array( $card, $disabled_cards, true ) ) {
throw new RuntimeException( throw new RuntimeException(
__( __(
'Unfortunately, we do not accept this card.', 'Unfortunately, we do not accept this card.',

View file

@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway;
use Dhii\Data\Container\ContainerInterface; use Dhii\Data\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn; use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail; use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail;
@ -1581,6 +1582,25 @@ return array(
if ( 'GB' === $country ) { if ( 'GB' === $country ) {
unset( $fields['disable_funding']['options']['card'] ); 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; return $fields;
}, },