Merge branch 'trunk' into PCP-114-sca-when-required

This commit is contained in:
dinamiko 2021-08-03 15:20:23 +02:00
commit 94aead33ba
41 changed files with 562 additions and 141 deletions

View file

@ -11,18 +11,24 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['7.3']
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- name: Validate composer.json and composer.lock - name: Validate composer.json and composer.lock
run: composer validate run: composer validate
- name: Install dependencies - name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest run: composer install --prefer-dist --no-progress --no-suggest
# Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit"
# Docs: https://getcomposer.org/doc/articles/scripts.md
- name: Run test suite - name: Run test suite
run: ./vendor/bin/phpunit run: ./vendor/bin/phpunit
- name: Run Woocommerce coding standards - name: Run Woocommerce coding standards

View file

@ -12,6 +12,7 @@ branches:
only: only:
- master - master
- trunk - trunk
- compat/ppxo
script: | script: |
CHANGED_FILES=`git diff --name-only --diff-filter=ACMR $TRAVIS_COMMIT_RANGE | grep \\\\.php | awk '{print}' ORS=' '` CHANGED_FILES=`git diff --name-only --diff-filter=ACMR $TRAVIS_COMMIT_RANGE | grep \\\\.php | awk '{print}' ORS=' '`

View file

@ -1,5 +1,21 @@
*** Changelog *** *** Changelog ***
= 1.4.0 - 2021-07-27 =
* Add - Venmo update #169
* Add - Pay Later Button Global Expansion #182
* Add - Add Canada to advanced credit and debit card #180
* Add - Add button height setting for mini cart #181
* Add - Add BN Code to Pay Later Messaging #183
* Add - Add 30 seconds timeout by default to all API requests #184
* Fix - ACDC checkout error: "Card Details not valid"; but payment completes #193
* Fix - Incorrect API credentials cause fatal error #187
* Fix - PayPal payment fails if a new user account is created during the checkout process #177
* Fix - Disabled PayPal button appears when another button is loaded on the same page #192
* Fix - [UNPROCESSABLE_ENTITY] error during checkout #172
* Fix - Do not send customer email when order status is on hold #173
* Fix - Remove merchant-id query parameter in JSSDK #179
* Fix - Error on Plugin activation with Zettle POS Integration for WooCommerce #195
= 1.3.2 - 2021-06-08 = = 1.3.2 - 2021-06-08 =
* Fix - Improve Subscription plugin support. #161 * Fix - Improve Subscription plugin support. #161
* Fix - Disable vault setting if vaulting feature is not available. #150 * Fix - Disable vault setting if vaulting feature is not available. #150

View file

@ -4,7 +4,7 @@
"description": "PayPal Commerce Platform for WooCommerce", "description": "PayPal Commerce Platform for WooCommerce",
"license": "GPL-2.0", "license": "GPL-2.0",
"require": { "require": {
"dhii/module-interface": "0.1", "dhii/module-interface": "^0.2 || ^0.3",
"psr/container": "1.0.0", "psr/container": "1.0.0",
"container-interop/service-provider": "^0.4.0", "container-interop/service-provider": "^0.4.0",
"dhii/containers": "v0.1.0-alpha1", "dhii/containers": "v0.1.0-alpha1",

View file

@ -36,7 +36,7 @@ class AdminNotices implements ModuleInterface {
* *
* @param ContainerInterface $container The container. * @param ContainerInterface $container The container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
add_action( add_action(
'admin_notices', 'admin_notices',
function() use ( $container ) { function() use ( $container ) {

View file

@ -120,7 +120,11 @@ class PayPalBearer implements Bearer {
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
$error = new RuntimeException( $error = new RuntimeException(
__( 'Could not create token.', 'woocommerce-paypal-payments' ) sprintf(
// translators: %s is the error description.
__( 'Could not create token. %s', 'woocommerce-paypal-payments' ),
isset( json_decode( $response['body'] )->error_description ) ? json_decode( $response['body'] )->error_description : ''
)
); );
$this->logger->log( $this->logger->log(
'warning', 'warning',

View file

@ -502,6 +502,14 @@ class OrderEndpoint {
return $order_to_update; return $order_to_update;
} }
$patches_array = $patches->to_array();
if ( ! isset( $patches_array[0]['value']['shipping'] ) ) {
$shipping = isset( $order_to_update->purchase_units()[0] ) && null !== $order_to_update->purchase_units()[0]->shipping() ? $order_to_update->purchase_units()[0]->shipping() : null;
if ( $shipping ) {
$patches_array[0]['value']['shipping'] = $shipping->to_array();
}
}
$bearer = $this->bearer->bearer(); $bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_to_update->id(); $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_to_update->id();
$args = array( $args = array(
@ -514,7 +522,7 @@ class OrderEndpoint {
$order_to_update $order_to_update
), ),
), ),
'body' => wp_json_encode( $patches->to_array() ), 'body' => wp_json_encode( $patches_array ),
); );
if ( $this->bn_code ) { if ( $this->bn_code ) {
$args['headers']['PayPal-Partner-Attribution-Id'] = $this->bn_code; $args['headers']['PayPal-Partner-Attribution-Id'] = $this->bn_code;

View file

@ -24,6 +24,8 @@ trait RequestTrait {
*/ */
private function request( string $url, array $args ) { private function request( string $url, array $args ) {
$args['timeout'] = 30;
/** /**
* This filter can be used to alter the request args. * This filter can be used to alter the request args.
* For example, during testing, the PayPal-Mock-Response header could be * For example, during testing, the PayPal-Mock-Response header could be

View file

@ -118,6 +118,24 @@ class DccApplies {
'JPY', 'JPY',
'USD', 'USD',
), ),
'CA' => array(
'AUD',
'CAD',
'CHF',
'CZK',
'DKK',
'EUR',
'GBP',
'HKD',
'HUF',
'JPY',
'NOK',
'NZD',
'PLN',
'SEK',
'SGD',
'USD',
),
); );
/** /**
@ -157,6 +175,12 @@ class DccApplies {
'amex' => array( 'USD' ), 'amex' => array( 'USD' ),
'discover' => array( 'USD' ), 'discover' => array( 'USD' ),
), ),
'CA' => array(
'mastercard' => array(),
'visa' => array(),
'amex' => array( 'CAD' ),
'jcb' => array( 'CAD' ),
),
); );
/** /**

View file

@ -37,7 +37,7 @@ class ApiModule implements ModuleInterface {
* *
* @param ContainerInterface $container The container. * @param ContainerInterface $container The container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
} }
/** /**

View file

@ -18,15 +18,17 @@ const bootstrap = () => {
const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages); const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages);
const context = PayPalCommerceGateway.context; const context = PayPalCommerceGateway.context;
if (context === 'mini-cart' || context === 'product') { if (context === 'mini-cart' || context === 'product') {
const miniCartBootstrap = new MiniCartBootstap( if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') {
PayPalCommerceGateway, const miniCartBootstrap = new MiniCartBootstap(
renderer PayPalCommerceGateway,
); renderer
);
miniCartBootstrap.init(); miniCartBootstrap.init();
}
} }
if (context === 'product') { if (context === 'product' && PayPalCommerceGateway.single_product_buttons_enabled === '1') {
const singleProductBootstrap = new SingleProductBootstap( const singleProductBootstrap = new SingleProductBootstap(
PayPalCommerceGateway, PayPalCommerceGateway,
renderer, renderer,

View file

@ -21,6 +21,8 @@ class CheckoutActionHandler {
const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review'; const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review';
const formValues = jQuery(formSelector).serialize(); const formValues = jQuery(formSelector).serialize();
const createaccount = jQuery('#createaccount').is(":checked") ? true : false;
return fetch(this.config.ajax.create_order.endpoint, { return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
@ -29,7 +31,8 @@ class CheckoutActionHandler {
bn_code:bnCode, bn_code:bnCode,
context:this.config.context, context:this.config.context,
order_id:this.config.order_id, order_id:this.config.order_id,
form:formValues form:formValues,
createaccount: createaccount
}) })
}).then(function (res) { }).then(function (res) {
return res.json(); return res.json();

View file

@ -39,4 +39,4 @@ class CartBootstrap {
} }
} }
export default CartBootstrap; export default CartBootstrap;

View file

@ -7,6 +7,7 @@ class CreditCardRenderer {
this.errorHandler = errorHandler; this.errorHandler = errorHandler;
this.spinner = spinner; this.spinner = spinner;
this.cardValid = false; this.cardValid = false;
this.formValid = false;
} }
render(wrapper, contextConfig) { render(wrapper, contextConfig) {
@ -97,12 +98,8 @@ class CreditCardRenderer {
event.preventDefault(); event.preventDefault();
} }
this.errorHandler.clear(); this.errorHandler.clear();
const state = hostedFields.getState();
const formValid = Object.keys(state.fields).every(function (key) {
return state.fields[key].isValid;
});
if (formValid && this.cardValid) { if (this.formValid && this.cardValid) {
const save_card = this.defaultConfig.save_card ? true : false; const save_card = this.defaultConfig.save_card ? true : false;
const vault = document.getElementById('ppcp-credit-card-vault') ? const vault = document.getElementById('ppcp-credit-card-vault') ?
document.getElementById('ppcp-credit-card-vault').checked : save_card; document.getElementById('ppcp-credit-card-vault').checked : save_card;
@ -134,6 +131,13 @@ class CreditCardRenderer {
const validCards = this.defaultConfig.hosted_fields.valid_cards; const validCards = this.defaultConfig.hosted_fields.valid_cards;
this.cardValid = validCards.indexOf(event.cards[0].type) !== -1; this.cardValid = validCards.indexOf(event.cards[0].type) !== -1;
}) })
hostedFields.on('validityChange', (event) => {
const formValid = Object.keys(event.fields).every(function (key) {
return event.fields[key].isValid;
});
this.formValid = formValid;
})
document.querySelector(wrapper + ' button').addEventListener( document.querySelector(wrapper + ' button').addEventListener(
'click', 'click',
submitEvent submitEvent

View file

@ -59,7 +59,6 @@ return array(
if ( $paypal_disabled ) { if ( $paypal_disabled ) {
return new DisabledSmartButton(); return new DisabledSmartButton();
} }
$payee_repository = $container->get( 'api.repository.payee' );
$payer_factory = $container->get( 'api.factory.payer' ); $payer_factory = $container->get( 'api.factory.payer' );
$request_data = $container->get( 'button.request-data' ); $request_data = $container->get( 'button.request-data' );
@ -69,11 +68,11 @@ return array(
$messages_apply = $container->get( 'button.helper.messages-apply' ); $messages_apply = $container->get( 'button.helper.messages-apply' );
$environment = $container->get( 'onboarding.environment' ); $environment = $container->get( 'onboarding.environment' );
$payment_token_repository = $container->get( 'subscription.repository.payment-token' ); $payment_token_repository = $container->get( 'subscription.repository.payment-token' );
$settings_status = $container->get( 'wcgateway.settings.status' );
return new SmartButton( return new SmartButton(
$container->get( 'button.url' ), $container->get( 'button.url' ),
$container->get( 'session.handler' ), $container->get( 'session.handler' ),
$settings, $settings,
$payee_repository,
$payer_factory, $payer_factory,
$client_id, $client_id,
$request_data, $request_data,
@ -81,7 +80,8 @@ return array(
$subscription_helper, $subscription_helper,
$messages_apply, $messages_apply,
$environment, $environment,
$payment_token_repository $payment_token_repository,
$settings_status
); );
}, },
'button.url' => static function ( $container ): string { 'button.url' => static function ( $container ): string {

View file

@ -11,7 +11,6 @@ namespace WooCommerce\PayPalCommerce\Button\Assets;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
@ -23,6 +22,7 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository; use WooCommerce\PayPalCommerce\Subscription\Repository\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use Woocommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/** /**
@ -30,6 +30,13 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/ */
class SmartButton implements SmartButtonInterface { class SmartButton implements SmartButtonInterface {
/**
* The Settings status helper.
*
* @var SettingsStatus
*/
protected $settings_status;
/** /**
* The URL to the module. * The URL to the module.
* *
@ -51,13 +58,6 @@ class SmartButton implements SmartButtonInterface {
*/ */
private $settings; private $settings;
/**
* The Payee Repository.
*
* @var PayeeRepository
*/
private $payee_repository;
/** /**
* The Payer Factory. * The Payer Factory.
* *
@ -120,7 +120,6 @@ class SmartButton implements SmartButtonInterface {
* @param string $module_url The URL to the module. * @param string $module_url The URL to the module.
* @param SessionHandler $session_handler The Session Handler. * @param SessionHandler $session_handler The Session Handler.
* @param Settings $settings The Settings. * @param Settings $settings The Settings.
* @param PayeeRepository $payee_repository The Payee Repository.
* @param PayerFactory $payer_factory The Payer factory. * @param PayerFactory $payer_factory The Payer factory.
* @param string $client_id The client ID. * @param string $client_id The client ID.
* @param RequestData $request_data The Request Data helper. * @param RequestData $request_data The Request Data helper.
@ -129,12 +128,12 @@ class SmartButton implements SmartButtonInterface {
* @param MessagesApply $messages_apply The Messages apply helper. * @param MessagesApply $messages_apply The Messages apply helper.
* @param Environment $environment The environment object. * @param Environment $environment The environment object.
* @param PaymentTokenRepository $payment_token_repository The payment token repository. * @param PaymentTokenRepository $payment_token_repository The payment token repository.
* @param SettingsStatus $settings_status The Settings status helper.
*/ */
public function __construct( public function __construct(
string $module_url, string $module_url,
SessionHandler $session_handler, SessionHandler $session_handler,
Settings $settings, Settings $settings,
PayeeRepository $payee_repository,
PayerFactory $payer_factory, PayerFactory $payer_factory,
string $client_id, string $client_id,
RequestData $request_data, RequestData $request_data,
@ -142,13 +141,13 @@ class SmartButton implements SmartButtonInterface {
SubscriptionHelper $subscription_helper, SubscriptionHelper $subscription_helper,
MessagesApply $messages_apply, MessagesApply $messages_apply,
Environment $environment, Environment $environment,
PaymentTokenRepository $payment_token_repository PaymentTokenRepository $payment_token_repository,
SettingsStatus $settings_status
) { ) {
$this->module_url = $module_url; $this->module_url = $module_url;
$this->session_handler = $session_handler; $this->session_handler = $session_handler;
$this->settings = $settings; $this->settings = $settings;
$this->payee_repository = $payee_repository;
$this->payer_factory = $payer_factory; $this->payer_factory = $payer_factory;
$this->client_id = $client_id; $this->client_id = $client_id;
$this->request_data = $request_data; $this->request_data = $request_data;
@ -157,6 +156,7 @@ class SmartButton implements SmartButtonInterface {
$this->messages_apply = $messages_apply; $this->messages_apply = $messages_apply;
$this->environment = $environment; $this->environment = $environment;
$this->payment_token_repository = $payment_token_repository; $this->payment_token_repository = $payment_token_repository;
$this->settings_status = $settings_status;
} }
/** /**
@ -202,7 +202,7 @@ class SmartButton implements SmartButtonInterface {
add_filter( add_filter(
'woocommerce_credit_card_form_fields', 'woocommerce_credit_card_form_fields',
function ( $default_fields, $id ) { function ( $default_fields, $id ) {
if ( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) && CreditCardGateway::ID === $id ) { if ( is_user_logged_in() && $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) && CreditCardGateway::ID === $id ) {
$default_fields['card-vault'] = sprintf( $default_fields['card-vault'] = sprintf(
'<p class="form-row form-row-wide"><label for="vault"><input class="ppcp-credit-card-vault" type="checkbox" id="ppcp-credit-card-vault" name="vault">%s</label></p>', '<p class="form-row form-row-wide"><label for="vault"><input class="ppcp-credit-card-vault" type="checkbox" id="ppcp-credit-card-vault" name="vault">%s</label></p>',
esc_html__( 'Save your Credit Card', 'woocommerce-paypal-payments' ) esc_html__( 'Save your Credit Card', 'woocommerce-paypal-payments' )
@ -438,7 +438,7 @@ class SmartButton implements SmartButtonInterface {
*/ */
public function message_renderer() { public function message_renderer() {
echo '<div id="ppcp-messages"></div>'; echo '<div id="ppcp-messages" data-partner-attribution-id="Woo_PPCP"></div>';
} }
/** /**
@ -612,16 +612,16 @@ class SmartButton implements SmartButtonInterface {
$this->request_data->enqueue_nonce_fix(); $this->request_data->enqueue_nonce_fix();
$localize = array( $localize = array(
'script_attributes' => $this->attributes(), 'script_attributes' => $this->attributes(),
'data_client_id' => array( 'data_client_id' => array(
'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(), 'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(),
'endpoint' => home_url( \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ) ), 'endpoint' => home_url( \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ) ),
'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ), 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
'user' => get_current_user_id(), 'user' => get_current_user_id(),
), ),
'redirect' => wc_get_checkout_url(), 'redirect' => wc_get_checkout_url(),
'context' => $this->context(), 'context' => $this->context(),
'ajax' => array( 'ajax' => array(
'change_cart' => array( 'change_cart' => array(
'endpoint' => home_url( \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ) ), 'endpoint' => home_url( \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ) ),
'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ), 'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ),
@ -635,11 +635,11 @@ class SmartButton implements SmartButtonInterface {
'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ), 'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ),
), ),
), ),
'enforce_vault' => $this->has_subscriptions(), 'enforce_vault' => $this->has_subscriptions(),
'save_card' => $this->can_save_vault_token(), 'save_card' => $this->can_save_vault_token(),
'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',
'mini_cart_wrapper' => '#ppc-button-minicart', 'mini_cart_wrapper' => '#ppc-button-minicart',
'cancel_wrapper' => '#ppcp-cancel', 'cancel_wrapper' => '#ppcp-cancel',
@ -650,6 +650,7 @@ class SmartButton implements SmartButtonInterface {
'shape' => $this->style_for_context( 'shape', 'mini-cart' ), 'shape' => $this->style_for_context( 'shape', 'mini-cart' ),
'label' => $this->style_for_context( 'label', 'mini-cart' ), 'label' => $this->style_for_context( 'label', 'mini-cart' ),
'tagline' => $this->style_for_context( 'tagline', 'mini-cart' ), 'tagline' => $this->style_for_context( 'tagline', 'mini-cart' ),
'height' => $this->settings->has( 'button_mini-cart_height' ) && $this->settings->get( 'button_mini-cart_height' ) ? $this->normalize_height( (int) $this->settings->get( 'button_mini-cart_height' ) ) : 35,
), ),
'style' => array( 'style' => array(
'layout' => $this->style_for_context( 'layout', $this->context() ), 'layout' => $this->style_for_context( 'layout', $this->context() ),
@ -659,7 +660,7 @@ class SmartButton implements SmartButtonInterface {
'tagline' => $this->style_for_context( 'tagline', $this->context() ), 'tagline' => $this->style_for_context( 'tagline', $this->context() ),
), ),
), ),
'hosted_fields' => array( 'hosted_fields' => array(
'wrapper' => '#ppcp-hosted-fields', 'wrapper' => '#ppcp-hosted-fields',
'mini_cart_wrapper' => '#ppcp-hosted-fields-mini-cart', 'mini_cart_wrapper' => '#ppcp-hosted-fields-mini-cart',
'labels' => array( 'labels' => array(
@ -677,8 +678,8 @@ class SmartButton implements SmartButtonInterface {
), ),
'valid_cards' => $this->dcc_applies->valid_cards(), 'valid_cards' => $this->dcc_applies->valid_cards(),
), ),
'messages' => $this->message_values(), 'messages' => $this->message_values(),
'labels' => array( 'labels' => array(
'error' => array( 'error' => array(
'generic' => __( 'generic' => __(
'Something went wrong. Please try again or choose another payment source.', 'Something went wrong. Please try again or choose another payment source.',
@ -686,7 +687,9 @@ class SmartButton implements SmartButtonInterface {
), ),
), ),
), ),
'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0, 'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0,
'single_product_buttons_enabled' => $this->settings->has( 'button_product_enabled' ) && $this->settings->get( 'button_product_enabled' ),
'mini_cart_buttons_enabled' => $this->settings->has( 'button_mini-cart_enabled' ) && $this->settings->get( 'button_mini-cart_enabled' ),
); );
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) { if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
@ -740,10 +743,6 @@ class SmartButton implements SmartButtonInterface {
) { ) {
$params['buyer-country'] = WC()->customer->get_billing_country(); $params['buyer-country'] = WC()->customer->get_billing_country();
} }
$payee = $this->payee_repository->payee();
if ( $payee->merchant_id() ) {
$params['merchant-id'] = $payee->merchant_id();
}
$disable_funding = $this->settings->has( 'disable_funding' ) ? $disable_funding = $this->settings->has( 'disable_funding' ) ?
$this->settings->get( 'disable_funding' ) : array(); $this->settings->get( 'disable_funding' ) : array();
if ( ! is_checkout() ) { if ( ! is_checkout() ) {
@ -754,6 +753,14 @@ class SmartButton implements SmartButtonInterface {
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) ); $params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
} }
$enable_funding = array( 'venmo' );
if ( $this->settings_status->pay_later_messaging_is_enabled() || ! in_array( 'credit', $disable_funding, true ) ) {
$enable_funding[] = 'paylater';
}
if ( count( $enable_funding ) > 0 ) {
$params['enable-funding'] = implode( ',', array_unique( $enable_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; return $smart_button_url;
} }
@ -939,4 +946,21 @@ class SmartButton implements SmartButtonInterface {
} }
return (string) $value; return (string) $value;
} }
/**
* Returns a value between 25 and 55.
*
* @param int $height The input value.
* @return int The normalized output value.
*/
private function normalize_height( int $height ): int {
if ( $height < 25 ) {
return 25;
}
if ( $height > 55 ) {
return 55;
}
return $height;
}
} }

View file

@ -174,7 +174,11 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->set_bn_code( $data ); $this->set_bn_code( $data );
if ( 'checkout' === $data['context'] ) { if ( 'checkout' === $data['context'] ) {
$this->process_checkout_form( $data['form'] ); if ( isset( $data['createaccount'] ) && '1' === $data['createaccount'] ) {
$this->process_checkout_form_when_creating_account( $data['form'], $wc_order );
}
$this->process_checkout_form( $data['form'] );
} }
if ( 'pay-now' === $data['context'] && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) { if ( 'pay-now' === $data['context'] && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) {
$this->validate_paynow_form( $data['form'] ); $this->validate_paynow_form( $data['form'] );
@ -201,6 +205,43 @@ class CreateOrderEndpoint implements EndpointInterface {
return false; return false;
} }
/**
* Once the checkout has been validated we execute this method.
*
* @param array $data The data.
* @param \WP_Error $errors The errors, which occurred.
*
* @return array
*/
public function after_checkout_validation( array $data, \WP_Error $errors ): array {
if ( ! $errors->errors ) {
$order = $this->create_paypal_order();
/**
* In case we are onboarded and everything is fine with the \WC_Order
* we want this order to be created. We will intercept it and leave it
* in the "Pending payment" status though, which than later will change
* during the "onApprove"-JS callback or the webhook listener.
*/
if ( ! $this->early_order_handler->should_create_early_order() ) {
wp_send_json_success( $order->to_array() );
}
$this->early_order_handler->register_for_order( $order );
return $data;
}
wp_send_json_error(
array(
'name' => '',
'message' => $errors->get_error_message(),
'code' => (int) $errors->get_error_code(),
'details' => array(),
)
);
return $data;
}
/** /**
* Creates the order in the PayPal, uses data from WC order if provided. * Creates the order in the PayPal, uses data from WC order if provided.
* *
@ -341,39 +382,40 @@ class CreateOrderEndpoint implements EndpointInterface {
} }
/** /**
* Once the checkout has been validated we execute this method. * Processes checkout and creates the PayPal order after success form validation.
* *
* @param array $data The data. * @param string $form_values The values of the form.
* @param \WP_Error $errors The errors, which occurred. * @param \WC_Order|null $wc_order WC order to get data from.
* * @throws \Exception On Error.
* @return array
*/ */
public function after_checkout_validation( array $data, \WP_Error $errors ): array { private function process_checkout_form_when_creating_account( string $form_values, \WC_Order $wc_order = null ) {
if ( ! $errors->errors ) { $form_values = explode( '&', $form_values );
$parsed_values = array();
foreach ( $form_values as $field ) {
$field = explode( '=', $field );
$order = $this->create_paypal_order(); if ( count( $field ) !== 2 ) {
continue;
/**
* In case we are onboarded and everything is fine with the \WC_Order
* we want this order to be created. We will intercept it and leave it
* in the "Pending payment" status though, which than later will change
* during the "onApprove"-JS callback or the webhook listener.
*/
if ( ! $this->early_order_handler->should_create_early_order() ) {
wp_send_json_success( $order->to_array() );
} }
$this->early_order_handler->register_for_order( $order ); $parsed_values[ $field[0] ] = $field[1];
return $data;
} }
$_POST = $parsed_values;
$_REQUEST = $parsed_values;
wp_send_json_error( add_action(
array( 'woocommerce_after_checkout_validation',
'name' => '', function ( array $data, \WP_Error $errors ) use ( $wc_order ) {
'message' => $errors->get_error_message(), if ( ! $errors->errors ) {
'code' => (int) $errors->get_error_code(), $order = $this->create_paypal_order( $wc_order );
'details' => array(), wp_send_json_success( $order->to_array() );
) return true;
}
},
10,
2
); );
return $data;
$checkout = \WC()->checkout();
$checkout->process_checkout();
} }
} }

View file

@ -43,7 +43,7 @@ class ButtonModule implements ModuleInterface {
* *
* @param ContainerInterface|null $container The Container. * @param ContainerInterface|null $container The Container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
add_action( add_action(
'wp', 'wp',

View file

@ -0,0 +1,12 @@
<?php
/**
* The compatibility module extensions.
*
* @package WooCommerce\PayPalCommerce\Compat
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat;
return array();

View file

@ -0,0 +1,16 @@
<?php
/**
* The compatibility module.
*
* @package WooCommerce\PayPalCommerce\Compat
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat;
use Dhii\Modular\Module\ModuleInterface;
return static function (): ModuleInterface {
return new CompatModule();
};

View file

@ -0,0 +1,12 @@
<?php
/**
* The compatibility module services.
*
* @package WooCommerce\PayPalCommerce\Compat
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat;
return array();

View file

@ -0,0 +1,49 @@
<?php
/**
* The compatibility module.
*
* @package WooCommerce\PayPalCommerce\Compat
*/
declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat;
use Dhii\Container\ServiceProvider;
use Dhii\Modular\Module\ModuleInterface;
use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
/**
* Class CompatModule
*/
class CompatModule implements ModuleInterface {
/**
* Setup the compatibility module.
*
* @return ServiceProviderInterface
*/
public function setup(): ServiceProviderInterface {
return new ServiceProvider(
require __DIR__ . '/../services.php',
require __DIR__ . '/../extensions.php'
);
}
/**
* Run the compatibility module.
*
* @param ContainerInterface|null $container The Container.
*/
public function run( ContainerInterface $container ): void {
}
/**
* Returns the key for the module.
*
* @return string|void
*/
public function getKey() {
}
}

View file

@ -40,7 +40,8 @@ class OnboardingModule implements ModuleInterface {
* *
* @param ContainerInterface|null $container The container. * @param ContainerInterface|null $container The container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
$asset_loader = $container->get( 'onboarding.assets' ); $asset_loader = $container->get( 'onboarding.assets' );
/** /**
* The OnboardingAssets. * The OnboardingAssets.

View file

@ -37,7 +37,7 @@ class SessionModule implements ModuleInterface {
* *
* @param ContainerInterface|null $container The container. * @param ContainerInterface|null $container The container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
add_action( add_action(
'woocommerce_init', 'woocommerce_init',
function () use ( $container ) { function () use ( $container ) {

View file

@ -44,7 +44,7 @@ class SubscriptionModule implements ModuleInterface {
* *
* @param ContainerInterface|null $container The container. * @param ContainerInterface|null $container The container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
add_action( add_action(
'woocommerce_scheduled_subscription_payment_' . PayPalGateway::ID, 'woocommerce_scheduled_subscription_payment_' . PayPalGateway::ID,
function ( $amount, $order ) use ( $container ) { function ( $amount, $order ) use ( $container ) {

View file

@ -25,6 +25,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use Woocommerce\PayPalCommerce\WcGateway\Helper\DccProductStatus; use Woocommerce\PayPalCommerce\WcGateway\Helper\DccProductStatus;
use Woocommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice; use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
@ -114,6 +115,10 @@ return array(
'wcgateway.settings.sections-renderer' => static function ( $container ): SectionsRenderer { 'wcgateway.settings.sections-renderer' => static function ( $container ): SectionsRenderer {
return new SectionsRenderer(); return new SectionsRenderer();
}, },
'wcgateway.settings.status' => static function ( $container ): SettingsStatus {
$settings = $container->get( 'wcgateway.settings' );
return new SettingsStatus( $settings );
},
'wcgateway.settings.render' => static function ( $container ): SettingsRenderer { 'wcgateway.settings.render' => static function ( $container ): SettingsRenderer {
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$state = $container->get( 'onboarding.state' ); $state = $container->get( 'onboarding.state' );
@ -121,13 +126,15 @@ return array(
$dcc_applies = $container->get( 'api.helpers.dccapplies' ); $dcc_applies = $container->get( 'api.helpers.dccapplies' );
$messages_apply = $container->get( 'button.helper.messages-apply' ); $messages_apply = $container->get( 'button.helper.messages-apply' );
$dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' ); $dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' );
$settings_status = $container->get( 'wcgateway.settings.status' );
return new SettingsRenderer( return new SettingsRenderer(
$settings, $settings,
$state, $state,
$fields, $fields,
$dcc_applies, $dcc_applies,
$messages_apply, $messages_apply,
$dcc_product_status $dcc_product_status,
$settings_status
); );
}, },
'wcgateway.settings.listener' => static function ( $container ): SettingsListener { 'wcgateway.settings.listener' => static function ( $container ): SettingsListener {
@ -828,7 +835,7 @@ return array(
), ),
'requirements' => array( 'messages' ), 'requirements' => array( 'messages' ),
'gateway' => 'paypal', 'gateway' => 'paypal',
'description' => str_replace( '<a>', '<a href="' . $messages_disclaimers->link_for_country() . '" target="_blank">', __( 'Displays Pay Later messaging for available offers. Restrictions apply. <a>Click here to learn more.</a>', 'woocommerce-paypal-payments' ) ), 'description' => str_replace( '<a>', '<a href="' . $messages_disclaimers->link_for_country() . '" target="_blank">', __( 'Displays Pay Later messaging for available offers. Restrictions apply. <a>Click here to learn more</a>. Pay Later button will show for eligible buyers and PayPal determines eligibility.', 'woocommerce-paypal-payments' ) ),
'class' => array( 'ppcp-subheading' ), 'class' => array( 'ppcp-subheading' ),
), ),
'message_enabled' => array( 'message_enabled' => array(
@ -1131,7 +1138,7 @@ return array(
), ),
'requirements' => array( 'messages' ), 'requirements' => array( 'messages' ),
'gateway' => 'paypal', 'gateway' => 'paypal',
'description' => str_replace( '<a>', '<a href="' . $messages_disclaimers->link_for_country() . '" target="_blank">', __( 'Displays Pay Later messaging for available offers. Restrictions apply. <a>Click here to learn more.</a>', 'woocommerce-paypal-payments' ) ), 'description' => str_replace( '<a>', '<a href="' . $messages_disclaimers->link_for_country() . '" target="_blank">', __( 'Displays Pay Later messaging for available offers. Restrictions apply. <a>Click here to learn more</a>. Pay Later button will show for eligible buyers and PayPal determines eligibility.', 'woocommerce-paypal-payments' ) ),
'class' => array( 'ppcp-subheading' ), 'class' => array( 'ppcp-subheading' ),
), ),
'message_product_enabled' => array( 'message_product_enabled' => array(
@ -1434,7 +1441,7 @@ return array(
), ),
'requirements' => array( 'messages' ), 'requirements' => array( 'messages' ),
'gateway' => 'paypal', 'gateway' => 'paypal',
'description' => str_replace( '<a>', '<a href="' . $messages_disclaimers->link_for_country() . '" target="_blank">', __( 'Displays Pay Later messaging for available offers. Restrictions apply. <a>Click here to learn more.</a>', 'woocommerce-paypal-payments' ) ), 'description' => str_replace( '<a>', '<a href="' . $messages_disclaimers->link_for_country() . '" target="_blank">', __( 'Displays Pay Later messaging for available offers. Restrictions apply. <a>Click here to learn more</a>. Pay Later button will show for eligible buyers and PayPal determines eligibility.', 'woocommerce-paypal-payments' ) ),
'class' => array( 'ppcp-subheading' ), 'class' => array( 'ppcp-subheading' ),
), ),
'message_cart_enabled' => array( 'message_cart_enabled' => array(
@ -1623,7 +1630,7 @@ return array(
'type' => 'select', 'type' => 'select',
'class' => array(), 'class' => array(),
'input_class' => array( 'wc-enhanced-select' ), 'input_class' => array( 'wc-enhanced-select' ),
'default' => 'horizontal', 'default' => 'vertical',
'desc_tip' => true, 'desc_tip' => true,
'description' => __( 'description' => __(
'If additional funding sources are available to the buyer through PayPal, such as Venmo, then multiple buttons are displayed in the space provided. Choose "vertical" for a dynamic list of alternative and local payment options, or "horizontal" when space is limited.', 'If additional funding sources are available to the buyer through PayPal, such as Venmo, then multiple buttons are displayed in the space provided. Choose "vertical" for a dynamic list of alternative and local payment options, or "horizontal" when space is limited.',
@ -1727,6 +1734,19 @@ return array(
'requirements' => array(), 'requirements' => array(),
'gateway' => 'paypal', 'gateway' => 'paypal',
), ),
'button_mini-cart_height' => array(
'title' => __( 'Button Height', 'woocommerce-paypal-payments' ),
'type' => 'number',
'default' => '35',
'desc_tip' => true,
'description' => __( 'Add a value from 25 to 55.', 'woocommerce-paypal-payments' ),
'screens' => array(
State::STATE_PROGRESSIVE,
State::STATE_ONBOARDED,
),
'requirements' => array(),
'gateway' => 'paypal',
),
'disable_cards' => array( 'disable_cards' => array(
'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ), 'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ),

View file

@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Assets; namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/** /**
* Class SettingsPageAssets * Class SettingsPageAssets
@ -111,13 +112,17 @@ class SettingsPageAssets {
true true
); );
$token = $bearer->bearer(); try {
wp_localize_script( $token = $bearer->bearer();
'ppcp-gateway-settings', wp_localize_script(
'PayPalCommerceGatewaySettings', 'ppcp-gateway-settings',
array( 'PayPalCommerceGatewaySettings',
'vaulting_features_available' => $token->vaulting_available(), array(
) 'vaulting_features_available' => $token->vaulting_available(),
); )
);
} catch ( RuntimeException $exception ) {
return;
}
} }
} }

View file

@ -96,6 +96,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/ */
private $refund_processor; private $refund_processor;
/**
* Whether the plugin is in onboarded state.
*
* @var bool
*/
private $onboarded;
/** /**
* PayPalGateway constructor. * PayPalGateway constructor.
* *
@ -132,8 +139,9 @@ class PayPalGateway extends \WC_Payment_Gateway {
$this->session_handler = $session_handler; $this->session_handler = $session_handler;
$this->refund_processor = $refund_processor; $this->refund_processor = $refund_processor;
$this->transaction_url_provider = $transaction_url_provider; $this->transaction_url_provider = $transaction_url_provider;
$this->onboarded = $state->current_state() === State::STATE_ONBOARDED;
if ( $state->current_state() === State::STATE_ONBOARDED ) { if ( $this->onboarded ) {
$this->supports = array( 'refunds' ); $this->supports = array( 'refunds' );
} }
if ( if (
@ -185,7 +193,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/ */
public function needs_setup(): bool { public function needs_setup(): bool {
return true; return ! $this->onboarded;
} }
/** /**
@ -373,4 +381,20 @@ class PayPalGateway extends \WC_Payment_Gateway {
return parent::get_transaction_url( $order ); return parent::get_transaction_url( $order );
} }
/**
* Updates WooCommerce gateway option.
*
* @param string $key The option key.
* @param string $value The option value.
* @return bool|void
*/
public function update_option( $key, $value = '' ) {
parent::update_option( $key, $value );
if ( 'enabled' === $key ) {
$this->config->set( 'enabled', 'yes' === $value );
$this->config->persist();
}
}
} }

View file

@ -0,0 +1,55 @@
<?php
/**
* Helper to get settings status.
*
* @package Woocommerce\PayPalCommerce\WcGateway\Helper
*/
declare(strict_types=1);
namespace Woocommerce\PayPalCommerce\WcGateway\Helper;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class SettingsStatus
*/
class SettingsStatus {
/**
* The Settings.
*
* @var Settings
*/
protected $settings;
/**
* SettingsStatus constructor.
*
* @param Settings $settings The Settings.
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
}
/**
* Check whether Pay Later message is enabled either for checkout, cart or product page.
*
* @return bool
* @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found.
*/
public function pay_later_messaging_is_enabled(): bool {
$pay_later_message_enabled_for_checkout = $this->settings->has( 'message_enabled' )
&& (bool) $this->settings->get( 'message_enabled' );
$pay_later_message_enabled_for_cart = $this->settings->has( 'message_cart_enabled' )
&& (bool) $this->settings->get( 'message_cart_enabled' );
$pay_later_message_enabled_for_product = $this->settings->has( 'message_product_enabled' )
&& (bool) $this->settings->get( 'message_product_enabled' );
return $pay_later_message_enabled_for_checkout ||
$pay_later_message_enabled_for_cart ||
$pay_later_message_enabled_for_product;
}
}

View file

@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
@ -147,11 +148,23 @@ class SettingsListener {
return; return;
} }
$token = $this->bearer->bearer(); try {
if ( ! $token->vaulting_available() ) { $token = $this->bearer->bearer();
$this->settings->set( 'vault_enabled', false ); if ( ! $token->vaulting_available() ) {
$this->settings->persist(); $this->settings->set( 'vault_enabled', false );
return; $this->settings->persist();
return;
}
} catch ( RuntimeException $exception ) {
add_action(
'admin_notices',
function () use ( $exception ) {
printf(
'<div class="notice notice-error"><p>%s</p></div>',
esc_attr( $exception->getMessage() )
);
}
);
} }
/** /**
@ -315,6 +328,7 @@ class SettingsListener {
$settings[ $key ] = isset( $raw_data[ $key ] ); $settings[ $key ] = isset( $raw_data[ $key ] );
break; break;
case 'text': case 'text':
case 'number':
case 'ppcp-text-input': case 'ppcp-text-input':
case 'ppcp-password': case 'ppcp-password':
$settings[ $key ] = isset( $raw_data[ $key ] ) ? sanitize_text_field( $raw_data[ $key ] ) : ''; $settings[ $key ] = isset( $raw_data[ $key ] ) ? sanitize_text_field( $raw_data[ $key ] ) : '';

View file

@ -16,12 +16,20 @@ use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Woocommerce\PayPalCommerce\WcGateway\Helper\DccProductStatus; use Woocommerce\PayPalCommerce\WcGateway\Helper\DccProductStatus;
use Woocommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
/** /**
* Class SettingsRenderer * Class SettingsRenderer
*/ */
class SettingsRenderer { class SettingsRenderer {
/**
* The Settings status helper.
*
* @var SettingsStatus
*/
protected $settings_status;
/** /**
* The settings. * The settings.
* *
@ -73,6 +81,7 @@ class SettingsRenderer {
* @param DccApplies $dcc_applies Whether DCC gateway can be shown. * @param DccApplies $dcc_applies Whether DCC gateway can be shown.
* @param MessagesApply $messages_apply Whether messages can be shown. * @param MessagesApply $messages_apply Whether messages can be shown.
* @param DccProductStatus $dcc_product_status The product status. * @param DccProductStatus $dcc_product_status The product status.
* @param SettingsStatus $settings_status The Settings status helper.
*/ */
public function __construct( public function __construct(
ContainerInterface $settings, ContainerInterface $settings,
@ -80,7 +89,8 @@ class SettingsRenderer {
array $fields, array $fields,
DccApplies $dcc_applies, DccApplies $dcc_applies,
MessagesApply $messages_apply, MessagesApply $messages_apply,
DccProductStatus $dcc_product_status DccProductStatus $dcc_product_status,
SettingsStatus $settings_status
) { ) {
$this->settings = $settings; $this->settings = $settings;
@ -89,6 +99,7 @@ class SettingsRenderer {
$this->dcc_applies = $dcc_applies; $this->dcc_applies = $dcc_applies;
$this->messages_apply = $messages_apply; $this->messages_apply = $messages_apply;
$this->dcc_product_status = $dcc_product_status; $this->dcc_product_status = $dcc_product_status;
$this->settings_status = $settings_status;
} }
/** /**
@ -106,7 +117,7 @@ class SettingsRenderer {
$pay_later_messages_title = __( 'Pay Later Messaging', 'woocommerce-paypal-payments' ); $pay_later_messages_title = __( 'Pay Later Messaging', 'woocommerce-paypal-payments' );
$enabled = $this->paypal_vaulting_is_enabled() ? $vaulting_title : $pay_later_messages_title; $enabled = $this->paypal_vaulting_is_enabled() ? $vaulting_title : $pay_later_messages_title;
$disabled = $this->pay_later_messaging_is_enabled() ? $vaulting_title : $pay_later_messages_title; $disabled = $this->settings_status->pay_later_messaging_is_enabled() ? $vaulting_title : $pay_later_messages_title;
$pay_later_messages_or_vaulting_text = sprintf( $pay_later_messages_or_vaulting_text = sprintf(
// translators: %1$s and %2$s is translated PayPal vaulting and Pay Later Messaging strings. // translators: %1$s and %2$s is translated PayPal vaulting and Pay Later Messaging strings.
@ -149,26 +160,6 @@ class SettingsRenderer {
return $this->settings->has( 'vault_enabled' ) && (bool) $this->settings->get( 'vault_enabled' ); return $this->settings->has( 'vault_enabled' ) && (bool) $this->settings->get( 'vault_enabled' );
} }
/**
* Check whether Pay Later message is enabled either for checkout, cart or product page.
*
* @return bool
*/
private function pay_later_messaging_is_enabled(): bool {
$pay_later_message_enabled_for_checkout = $this->settings->has( 'message_enabled' )
&& (bool) $this->settings->get( 'message_enabled' );
$pay_later_message_enabled_for_cart = $this->settings->has( 'message_cart_enabled' )
&& (bool) $this->settings->get( 'message_cart_enabled' );
$pay_later_message_enabled_for_product = $this->settings->has( 'message_product_enabled' )
&& (bool) $this->settings->get( 'message_product_enabled' );
return $pay_later_message_enabled_for_checkout ||
$pay_later_message_enabled_for_cart ||
$pay_later_message_enabled_for_product;
}
/** /**
* Check if current screen is PayPal checkout settings screen. * Check if current screen is PayPal checkout settings screen.
* *
@ -557,6 +548,6 @@ class SettingsRenderer {
} }
return $this->is_paypal_checkout_screen() && $this->paypal_vaulting_is_enabled() return $this->is_paypal_checkout_screen() && $this->paypal_vaulting_is_enabled()
|| $this->is_paypal_checkout_screen() && $this->pay_later_messaging_is_enabled(); || $this->is_paypal_checkout_screen() && $this->settings_status->pay_later_messaging_is_enabled();
} }
} }

View file

@ -53,7 +53,7 @@ class WcGatewayModule implements ModuleInterface {
* *
* @param ContainerInterface|null $container The container. * @param ContainerInterface|null $container The container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
$this->register_payment_gateways( $container ); $this->register_payment_gateways( $container );
$this->register_order_functionality( $container ); $this->register_order_functionality( $container );
$this->register_columns( $container ); $this->register_columns( $container );
@ -135,6 +135,18 @@ class WcGatewayModule implements ModuleInterface {
$endpoint->handle_request(); $endpoint->handle_request();
} }
); );
add_filter(
'woocommerce_email_recipient_customer_on_hold_order',
function( $recipient, $order ) {
if ( $order->get_payment_method() === PayPalGateway::ID || $order->get_payment_method() === CreditCardGateway::ID ) {
$recipient = '';
}
return $recipient;
},
10,
2
);
} }
/** /**

View file

@ -36,7 +36,7 @@ class WebhookModule implements ModuleInterface {
* *
* @param ContainerInterface|null $container The Container. * @param ContainerInterface|null $container The Container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
add_action( add_action(
'rest_api_init', 'rest_api_init',
static function () use ( $container ) { static function () use ( $container ) {

View file

@ -36,7 +36,7 @@ class WooCommerceLoggingModule implements ModuleInterface {
* *
* @param ContainerInterface $container The container. * @param ContainerInterface $container The container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
} }

View file

@ -1,6 +1,6 @@
{ {
"name": "woocommerce-paypal-payments", "name": "woocommerce-paypal-payments",
"version": "1.3.2", "version": "1.4.0",
"description": "WooCommerce PayPal Payments", "description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0", "license": "GPL-2.0",

View file

@ -4,7 +4,7 @@
<!-- Configs --> <!-- Configs -->
<config name="minimum_supported_wp_version" value="4.7" /> <config name="minimum_supported_wp_version" value="4.7" />
<config name="testVersion" value="7.0-" /> <config name="testVersion" value="7.1-" />
<!-- Rules --> <!-- Rules -->
<rule ref="WooCommerce-Core" /> <rule ref="WooCommerce-Core" />

View file

@ -2,9 +2,9 @@
Contributors: woocommerce, automattic Contributors: woocommerce, automattic
Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell, shop, shopping, cart, checkout Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell, shop, shopping, cart, checkout
Requires at least: 5.3 Requires at least: 5.3
Tested up to: 5.7 Tested up to: 5.8
Requires PHP: 7.1 Requires PHP: 7.1
Stable tag: 1.3.2 Stable tag: 1.4.0
License: GPLv2 License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html License URI: http://www.gnu.org/licenses/gpl-2.0.html
@ -58,6 +58,22 @@ Follow the steps below to connect the plugin to your PayPal account:
== Changelog == == Changelog ==
= 1.4.0 =
* Add - Venmo update #169
* Add - Pay Later Button Global Expansion #182
* Add - Add Canada to advanced credit and debit card #180
* Add - Add button height setting for mini cart #181
* Add - Add BN Code to Pay Later Messaging #183
* Add - Add 30 seconds timeout by default to all API requests #184
* Fix - ACDC checkout error: "Card Details not valid"; but payment completes #193
* Fix - Incorrect API credentials cause fatal error #187
* Fix - PayPal payment fails if a new user account is created during the checkout process #177
* Fix - Disabled PayPal button appears when another button is loaded on the same page #192
* Fix - [UNPROCESSABLE_ENTITY] error during checkout #172
* Fix - Do not send customer email when order status is on hold #173
* Fix - Remove merchant-id query parameter in JSSDK #179
* Fix - Error on Plugin activation with Zettle POS Integration for WooCommerce #195
= 1.3.2 = = 1.3.2 =
* Fix - Improve Subscription plugin support. #161 * Fix - Improve Subscription plugin support. #161
* Fix - Disable vault setting if vaulting feature is not available. #150 * Fix - Disable vault setting if vaulting feature is not available. #150

View file

@ -31,7 +31,7 @@ class PluginModule implements ModuleInterface {
* *
* @param ContainerInterface|null $container The Container. * @param ContainerInterface|null $container The Container.
*/ */
public function run( ContainerInterface $container = null ) { public function run( ContainerInterface $container ): void {
} }
/** /**

View file

@ -432,6 +432,9 @@ class OrderEndpointTest extends TestCase
$orderToUpdate $orderToUpdate
->shouldReceive('id') ->shouldReceive('id')
->andReturn($orderId); ->andReturn($orderId);
$orderToUpdate
->shouldReceive('purchase_units')
->andReturn([]);
$orderToCompare = Mockery::mock(Order::class); $orderToCompare = Mockery::mock(Order::class);
$rawResponse = ['body' => '{"is_correct":true}']; $rawResponse = ['body' => '{"is_correct":true}'];
@ -526,6 +529,9 @@ class OrderEndpointTest extends TestCase
$orderToUpdate $orderToUpdate
->shouldReceive('id') ->shouldReceive('id')
->andReturn($orderId); ->andReturn($orderId);
$orderToUpdate
->shouldReceive('purchase_units')
->andReturn([]);
$orderToCompare = Mockery::mock(Order::class); $orderToCompare = Mockery::mock(Order::class);
$rawResponse = ['body' => '{"has_error":true}']; $rawResponse = ['body' => '{"has_error":true}'];
@ -614,6 +620,9 @@ class OrderEndpointTest extends TestCase
$orderToUpdate $orderToUpdate
->shouldReceive('id') ->shouldReceive('id')
->andReturn($orderId); ->andReturn($orderId);
$orderToUpdate
->shouldReceive('purchase_units')
->andReturn([]);
$orderToCompare = Mockery::mock(Order::class); $orderToCompare = Mockery::mock(Order::class);
$rawResponse = ['body' => '{"is_correct":true}']; $rawResponse = ['body' => '{"is_correct":true}'];

View file

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway; namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
@ -365,6 +366,45 @@ class WcGatewayTest extends TestCase
$this->assertFalse($testee->capture_authorized_payment($wcOrder)); $this->assertFalse($testee->capture_authorized_payment($wcOrder));
} }
/**
* @dataProvider dataForTestNeedsSetup
*/
public function testNeedsSetup($currentState, $needSetup)
{
expect('is_admin')->andReturn(true);
$settingsRenderer = Mockery::mock(SettingsRenderer::class);
$orderProcessor = Mockery::mock(OrderProcessor::class);
$authorizedOrdersProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
$authorizeOrderActionNotice = Mockery::mock(AuthorizeOrderActionNotice::class);
$config = Mockery::mock(ContainerInterface::class);
$config
->shouldReceive('has')
->andReturn(false);
$sessionHandler = Mockery::mock(SessionHandler::class);
$refundProcessor = Mockery::mock(RefundProcessor::class);
$onboardingState = Mockery::mock(State::class);
$onboardingState
->expects('current_state')
->andReturn($currentState);
$transactionUrlProvider = Mockery::mock(TransactionUrlProvider::class);
$subscriptionHelper = Mockery::mock(SubscriptionHelper::class);
$testee = new PayPalGateway(
$settingsRenderer,
$orderProcessor,
$authorizedOrdersProcessor,
$authorizeOrderActionNotice,
$config,
$sessionHandler,
$refundProcessor,
$onboardingState,
$transactionUrlProvider,
$subscriptionHelper
);
$this->assertSame($needSetup, $testee->needs_setup());
}
public function dataForTestCaptureAuthorizedPaymentNoActionableFailures() : array public function dataForTestCaptureAuthorizedPaymentNoActionableFailures() : array
{ {
@ -383,4 +423,13 @@ class WcGatewayTest extends TestCase
], ],
]; ];
} }
public function dataForTestNeedsSetup(): array
{
return [
[State::STATE_START, true],
[State::STATE_PROGRESSIVE, true],
[State::STATE_ONBOARDED, false]
];
}
} }

View file

@ -3,13 +3,13 @@
* Plugin Name: WooCommerce PayPal Payments * Plugin Name: WooCommerce PayPal Payments
* Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/
* Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage.
* Version: 1.3.2 * Version: 1.4.0
* Author: WooCommerce * Author: WooCommerce
* Author URI: https://woocommerce.com/ * Author URI: https://woocommerce.com/
* License: GPL-2.0 * License: GPL-2.0
* Requires PHP: 7.1 * Requires PHP: 7.1
* WC requires at least: 3.9 * WC requires at least: 3.9
* WC tested up to: 4.9 * WC tested up to: 5.5
* Text Domain: woocommerce-paypal-payments * Text Domain: woocommerce-paypal-payments
* *
* @package WooCommerce\PayPalCommerce * @package WooCommerce\PayPalCommerce