From cda885d528ee203a9dfecbb9d007c3c59cf22938 Mon Sep 17 00:00:00 2001 From: support Date: Mon, 8 Nov 2021 14:58:25 -0500 Subject: [PATCH 01/44] introduce woocommerce_paypal_payments_product_supports_prb filter --- modules/ppcp-button/src/Assets/SmartButton.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index e83510873..8ee890e90 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -420,11 +420,10 @@ class SmartButton implements SmartButtonInterface { */ public function button_renderer() { $product = wc_get_product(); + if ( ! is_checkout() && is_a( $product, \WC_Product::class ) - && ( - $product->is_type( array( 'external', 'grouped' ) ) - || ! $product->is_in_stock() + && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_prb', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) ) ) { return; @@ -437,6 +436,15 @@ class SmartButton implements SmartButtonInterface { * Renders the HTML for the credit messaging. */ public function message_renderer() { + $product = wc_get_product(); + + if ( + ! is_checkout() && is_a( $product, \WC_Product::class ) + && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_prb', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) + ) + ) { + return; + } echo '
'; } From 587d3eef53db73afaeffaccbf0a8cd992390b2a3 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 20 Nov 2021 12:51:11 -0300 Subject: [PATCH 02/44] Disable the vaulting checkbox only when vaulting isn't in the scope Now, as long as vaulting is in the PayPal account's scope, the checkbox would always be enabled. Independently from the Pay Later settings. --- .../ppcp-wc-gateway/resources/js/gateway-settings.js | 10 +++++----- modules/ppcp-wc-gateway/services.php | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 69ae208aa..532d172bb 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -9,6 +9,10 @@ "#ppcp-vault_enabled" ) + const disabledCheckboxes = document.querySelectorAll( + '.ppc-disabled-checkbox' + ) + function atLeastOneChecked(checkboxesNodeList) { return Array.prototype.slice.call(checkboxesNodeList).filter(node => !node.disabled && node.checked).length > 0 } @@ -22,12 +26,8 @@ } function updateCheckboxes() { - atLeastOneChecked(payLaterMessagingCheckboxes) ? disableAll(vaultingCheckboxes) : enableAll(vaultingCheckboxes) + disableAll( disabledCheckboxes ); atLeastOneChecked(vaultingCheckboxes) ? disableAll(payLaterMessagingCheckboxes) : enableAll(payLaterMessagingCheckboxes) - - if(typeof PayPalCommerceGatewaySettings === 'undefined' || PayPalCommerceGatewaySettings.vaulting_features_available !== '1' ) { - disableAll(vaultingCheckboxes) - } } updateCheckboxes() diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index c806c76e5..3f51b9a99 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -719,6 +719,7 @@ return array( ), 'requirements' => array(), 'gateway' => array( 'paypal', 'dcc' ), + 'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppc-disabled-checkbox' ), ), 'logging_enabled' => array( 'title' => __( 'Logging', 'woocommerce-paypal-payments' ), @@ -2026,4 +2027,13 @@ return array( 'button.helper.messages-disclaimers' => static function ( ContainerInterface $container ): MessagesDisclaimers { return new MessagesDisclaimers(); }, + + 'wcgateway.helper.vaulting-scope' => static function ( ContainerInterface $container ): bool { + try { + $token = $container->get( 'api.bearer' )->bearer(); + return $token->vaulting_available(); + } catch ( RuntimeException $exception ) { + return false; + } + }, ); From abe4de252b40ba73f2590f05dccf643ea29f25fc Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 20 Nov 2021 12:58:56 -0300 Subject: [PATCH 03/44] Mention enabling vaulting from the PayPal account only when it was disabled there We were always displaying the text 'To use vaulting features, you must enable vaulting on your account' next to the checkbox for enabling vaulting. This message is irrelevant if vaulting is already enabled in the account. This commit makes that message to be displayed only when vaulting is disabled in the account. --- modules/ppcp-wc-gateway/services.php | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 3f51b9a99..72747b566 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -703,15 +703,7 @@ return array( 'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', 'desc_tip' => true, - 'label' => sprintf( - // translators: %1$s and %2$s are the opening and closing of HTML tag. - __( 'Enable saved cards and subscription features on your store. To use vaulting features, you must %1$senable vaulting on your account%2$s.', 'woocommerce-paypal-payments' ), - '', - '' - ), + 'label' => $container->get( 'button.helper.vaulting-label' ), 'description' => __( 'Allow registered buyers to save PayPal and Credit Card accounts. Allow Subscription renewals.', 'woocommerce-paypal-payments' ), 'default' => false, 'screens' => array( @@ -2036,4 +2028,22 @@ return array( return false; } }, + + 'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string { + $vaulting_label = __( 'Enable saved cards and subscription features on your store.', 'woocommerce-paypal-payments' ); + + if ( ! $container->get( 'wcgateway.helper.vaulting-scope' ) ) { + $vaulting_label .= sprintf( + // translators: %1$s and %2$s are the opening and closing of HTML tag. + __( ' To use vaulting features, you must %1$senable vaulting on your account%2$s.', 'woocommerce-paypal-payments' ), + '', + '' + ); + } + + return $vaulting_label; + }, ); From 1d089cbfa5bd1881610b85046f1e0f30d656ed59 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 20 Nov 2021 13:01:13 -0300 Subject: [PATCH 04/44] Add a description to the vaulting checkbox Enabling vaulting will disable Pay Later features. The description should make merchants aware of that. --- modules/ppcp-wc-gateway/services.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 72747b566..4c1041cb9 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2044,6 +2044,10 @@ return array( ); } + $vaulting_label .= '

'; + $vaulting_label .= __( 'This will disable all Pay Later messaging on your site.', 'woocommerce-paypal-payments' ); + $vaulting_label .= '

'; + return $vaulting_label; }, ); From e4c39fc2e3ce9f59cd46959fb1428f9cc010711b Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 20 Nov 2021 13:10:37 -0300 Subject: [PATCH 05/44] Remove unused localized variable and class property Because we're not checking whether vaulting is in the account's scope in the client side, we don't need to localize this variable and the class SettingsPageAssets doesn't need the property anymore, thus removing these. --- .../src/Assets/SettingsPageAssets.php | 34 +++---------------- .../ppcp-wc-gateway/src/WCGatewayModule.php | 3 +- .../Assets/SettingsPagesAssetsTest.php | 3 +- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php index db22c2c2f..222aa02ae 100644 --- a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php +++ b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php @@ -9,7 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway\Assets; -use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; /** @@ -31,34 +30,24 @@ class SettingsPageAssets { */ private $module_path; - /** - * The bearer. - * - * @var Bearer - */ - private $bearer; - /** * Assets constructor. * * @param string $module_url The url of this module. * @param string $module_path The filesystem path to this module. - * @param Bearer $bearer The bearer. */ - public function __construct( string $module_url, string $module_path, Bearer $bearer ) { + public function __construct( string $module_url, string $module_path ) { $this->module_url = $module_url; $this->module_path = $module_path; - $this->bearer = $bearer; } /** * Register assets provided by this module. */ public function register_assets() { - $bearer = $this->bearer; add_action( 'admin_enqueue_scripts', - function() use ( $bearer ) { + function() { if ( ! is_admin() || is_ajax() ) { return; } @@ -67,7 +56,7 @@ class SettingsPageAssets { return; } - $this->register_admin_assets( $bearer ); + $this->register_admin_assets(); } ); @@ -98,10 +87,8 @@ class SettingsPageAssets { /** * Register assets for admin pages. - * - * @param Bearer $bearer The bearer. */ - private function register_admin_assets( Bearer $bearer ) { + private function register_admin_assets() { $gateway_settings_script_path = trailingslashit( $this->module_path ) . 'assets/js/gateway-settings.js'; wp_enqueue_script( @@ -111,18 +98,5 @@ class SettingsPageAssets { file_exists( $gateway_settings_script_path ) ? (string) filemtime( $gateway_settings_script_path ) : null, true ); - - try { - $token = $bearer->bearer(); - wp_localize_script( - 'ppcp-gateway-settings', - 'PayPalCommerceGatewaySettings', - array( - 'vaulting_features_available' => $token->vaulting_available(), - ) - ); - } catch ( RuntimeException $exception ) { - return; - } } } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 7e90955d6..ea9104604 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -73,8 +73,7 @@ class WCGatewayModule implements ModuleInterface { if ( $c->has( 'wcgateway.url' ) ) { $assets = new SettingsPageAssets( $c->get( 'wcgateway.url' ), - $c->get( 'wcgateway.absolute-path' ), - $c->get( 'api.bearer' ) + $c->get( 'wcgateway.absolute-path' ) ); $assets->register_assets(); } diff --git a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php index 51179aa9c..f057a5167 100644 --- a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php +++ b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php @@ -13,9 +13,8 @@ class SettingsPagesAssetsTest extends TestCase { $moduleUrl = 'http://example.com/wp-content/plugins/woocommerce-paypal-payments/modules/ppcp-wc-gateway'; $modulePath = '/var/www/html/wp-content/plugins/woocommerce-paypal-payments/modules/ppcp-wc-gateway'; - $bearer = Mockery::mock(Bearer::class); - $testee = new SettingsPageAssets($moduleUrl, $modulePath, $bearer); + $testee = new SettingsPageAssets($moduleUrl, $modulePath); when('is_admin') ->justReturn(true); From 0f63be18de54d65c76931584767d1315ab42085b Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 20 Nov 2021 13:23:05 -0300 Subject: [PATCH 06/44] Don't display the admin notice saying that vaulting is disabled because of Pay Later We're not disabling vaulting checkboxes when Pay Later is enabled anymore so this admin notice is innacurate. --- .../src/Settings/SettingsRenderer.php | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php index 3ffcaf3e0..37135d45d 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php @@ -124,23 +124,12 @@ class SettingsRenderer { $messages = array(); - if ( $this->can_display_vaulting_admin_message() ) { - - $vaulting_title = __( 'PayPal vaulting', '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; - $disabled = $this->settings_status->pay_later_messaging_is_enabled() ? $vaulting_title : $pay_later_messages_title; - - $pay_later_messages_or_vaulting_text = sprintf( - // translators: %1$s and %2$s is translated PayPal vaulting and Pay Later Messaging strings. - __( - 'You have %1$s enabled, that\'s why %2$s options are unavailable now. You cannot use both features at the same time.', - 'woocommerce-paypal-payments' - ), - $enabled, - $disabled + if ( $this->can_display_vaulting_admin_message() && $this->paypal_vaulting_is_enabled() ) { + $pay_later_messages_or_vaulting_text = __( + "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", + 'woocommerce-paypal-payments' ); + $messages[] = new Message( $pay_later_messages_or_vaulting_text, 'warning' ); } From a338e727fdcef5b6b5a3bae337c0bbb97689076f Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 20 Nov 2021 14:38:36 -0300 Subject: [PATCH 07/44] Switch Pay Later labels when vaulting is toggled This way we'd display the 'Enable' label when Pay Later can be enabled, and the reason why it can't be enabled when vaulting is enabled. --- .../resources/js/gateway-settings.js | 39 ++++++++++++++++--- modules/ppcp-wc-gateway/services.php | 26 ++++++------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 532d172bb..fc6dec8ba 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -9,6 +9,14 @@ "#ppcp-vault_enabled" ) + const payLaterEnabledLabels = document.querySelectorAll( + ".ppcp-pay-later-enabled-label" + ) + + const payLaterDisabledLabels = document.querySelectorAll( + ".ppcp-pay-later-disabled-label" + ) + const disabledCheckboxes = document.querySelectorAll( '.ppc-disabled-checkbox' ) @@ -25,14 +33,33 @@ nodeList.forEach(node => node.removeAttribute('disabled')) } - function updateCheckboxes() { - disableAll( disabledCheckboxes ); - atLeastOneChecked(vaultingCheckboxes) ? disableAll(payLaterMessagingCheckboxes) : enableAll(payLaterMessagingCheckboxes) + function hideAll(nodeList) { + nodeList.forEach(node => node.style.display = 'none') } - updateCheckboxes() + function displayAll(nodeList) { + nodeList.forEach(node => node.style.display = '') + } - payLaterMessagingCheckboxes.forEach(node => node.addEventListener('change', updateCheckboxes)) - vaultingCheckboxes.forEach(node => node.addEventListener('change', updateCheckboxes)); + function disablePayLater() { + disableAll(payLaterMessagingCheckboxes) + hideAll(payLaterEnabledLabels) + displayAll(payLaterDisabledLabels) + } + + function enablePayLater() { + enableAll(payLaterMessagingCheckboxes) + displayAll(payLaterEnabledLabels) + hideAll(payLaterDisabledLabels) + } + + function togglePayLater() { + atLeastOneChecked(vaultingCheckboxes) ? disablePayLater() : enablePayLater() + } + + togglePayLater() + disableAll( disabledCheckboxes ) + + vaultingCheckboxes.forEach(node => node.addEventListener('change', togglePayLater)); } ); diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index 4c1041cb9..bb29333b4 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -894,7 +894,7 @@ return array( 'message_enabled' => array( 'title' => __( 'Enable message on Checkout', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => __( 'Enable on Checkout', 'woocommerce-paypal-payments' ), + 'label' => sprintf( $container->get( 'button.helper.pay-later-label' ), __( 'Enable on Checkout', 'woocommerce-paypal-payments' ) ), 'default' => true, 'screens' => array( State::STATE_PROGRESSIVE, @@ -1197,7 +1197,7 @@ return array( 'message_product_enabled' => array( 'title' => __( 'Enable message on Single Product', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => __( 'Enable on Single Product', 'woocommerce-paypal-payments' ), + 'label' => sprintf( $container->get( 'button.helper.pay-later-label' ), __( 'Enable on Single Product', 'woocommerce-paypal-payments' ) ), 'default' => true, 'screens' => array( State::STATE_PROGRESSIVE, @@ -1500,7 +1500,7 @@ return array( 'message_cart_enabled' => array( 'title' => __( 'Enable message on Cart', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => __( 'Enable on Cart', 'woocommerce-paypal-payments' ), + 'label' => sprintf( $container->get( 'button.helper.pay-later-label' ), __( 'Enable on Cart', 'woocommerce-paypal-payments' ) ), 'default' => true, 'screens' => array( State::STATE_PROGRESSIVE, @@ -1948,17 +1948,6 @@ return array( $fields['disable_cards']['options'] = $card_options; $fields['card_icons']['options'] = $card_options; - /** - * Display vault message on Pay Later label if vault is enabled. - */ - $settings = $container->get( 'wcgateway.settings' ); - if ( $settings->has( 'vault_enabled' ) && $settings->get( 'vault_enabled' ) ) { - $message = __( "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", 'woocommerce-paypal-payments' ); - $fields['message_enabled']['label'] = $message; - $fields['message_product_enabled']['label'] = $message; - $fields['message_cart_enabled']['label'] = $message; - } - return $fields; }, @@ -2050,4 +2039,13 @@ return array( return $vaulting_label; }, + + 'button.helper.pay-later-label' => static function ( ContainerInterface $container ): string { + $pay_later_label = '%s'; + $pay_later_label .= ''; + $pay_later_label .= __( "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", 'woocommerce-paypal-payments' ); + $pay_later_label .= ''; + + return $pay_later_label; + }, ); From 004c7f7d586a84dd1d5eb248cd86e2aafe544c32 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Sat, 20 Nov 2021 14:42:09 -0300 Subject: [PATCH 08/44] Remove admin notice saying that Pay Later is disabled because of vaulting We're now displaying this message on each Pay Later label when vaulting is enabled. Also, this message persists when vaulting is toggled, while the labels are updated dynamically. Removing this notice because of these. --- .../ppcp-wc-gateway/src/Settings/SettingsRenderer.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php index 37135d45d..97e991630 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php @@ -124,15 +124,6 @@ class SettingsRenderer { $messages = array(); - if ( $this->can_display_vaulting_admin_message() && $this->paypal_vaulting_is_enabled() ) { - $pay_later_messages_or_vaulting_text = __( - "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", - 'woocommerce-paypal-payments' - ); - - $messages[] = new Message( $pay_later_messages_or_vaulting_text, 'warning' ); - } - //phpcs:disable WordPress.Security.NonceVerification.Recommended //phpcs:disable WordPress.Security.NonceVerification.Missing if ( ! isset( $_GET['ppcp-onboarding-error'] ) || ! empty( $_POST ) ) { From 3d0309711085d09d675cdf323947a0507fa113bc Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 22 Nov 2021 12:29:52 -0300 Subject: [PATCH 09/44] Uncheck the Pay Later checkboxes before disabling them when enabling vaulting This way the children settings of Pay Later get hidden, keeping the interface consistent. --- modules/ppcp-wc-gateway/resources/js/gateway-settings.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index fc6dec8ba..6f371dd99 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -41,7 +41,15 @@ nodeList.forEach(node => node.style.display = '') } + function uncheckAll(nodeList){ + nodeList.forEach(node => { + node.checked = false + node.dispatchEvent(new Event('change')) + }) + } + function disablePayLater() { + uncheckAll(payLaterMessagingCheckboxes) disableAll(payLaterMessagingCheckboxes) hideAll(payLaterEnabledLabels) displayAll(payLaterDisabledLabels) From b42e3d56231dc93b65cf63a11f87e0f0ce975eab Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 22 Nov 2021 12:53:00 -0300 Subject: [PATCH 10/44] Disable the Vaulting checkbox on page load before handling the UI Otherwise the Pay Later options would stay disabled when vaulting was disabled from the PayPal account. --- modules/ppcp-wc-gateway/resources/js/gateway-settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 6f371dd99..633cb59f5 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -65,8 +65,8 @@ atLeastOneChecked(vaultingCheckboxes) ? disablePayLater() : enablePayLater() } - togglePayLater() disableAll( disabledCheckboxes ) + togglePayLater() vaultingCheckboxes.forEach(node => node.addEventListener('change', togglePayLater)); } From 9da36095e7d54c5bee2231aca4f61bcc0353b86b Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Mon, 22 Nov 2021 13:17:54 -0300 Subject: [PATCH 11/44] Fix phpcs warning --- modules/ppcp-wc-gateway/services.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index bb29333b4..d031ad846 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -2040,7 +2040,7 @@ return array( return $vaulting_label; }, - 'button.helper.pay-later-label' => static function ( ContainerInterface $container ): string { + 'button.helper.pay-later-label' => static function ( ContainerInterface $container ): string { $pay_later_label = '%s'; $pay_later_label .= ''; $pay_later_label .= __( "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", 'woocommerce-paypal-payments' ); From 2ddd8518caaede426f6fe24f4f54fd0cd4e80df6 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Tue, 23 Nov 2021 17:51:59 -0300 Subject: [PATCH 12/44] Add missing 'use' of the RuntimeException class --- modules/ppcp-wc-gateway/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index d031ad846..e2616fca4 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -13,6 +13,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway; use Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext; +use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers; From a4846b64fe250c62a0c084ea5aeeb3af85cc6791 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Tue, 23 Nov 2021 22:12:32 -0300 Subject: [PATCH 13/44] Fix wrong prefix for a newly added class for disabled checkbox The prefix for the plugin is 'ppcp', not 'ppc' as it mistakenly was. Updating this for a checkbox class. --- modules/ppcp-wc-gateway/resources/js/gateway-settings.js | 2 +- modules/ppcp-wc-gateway/services.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index 633cb59f5..8e8cfe21b 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -18,7 +18,7 @@ ) const disabledCheckboxes = document.querySelectorAll( - '.ppc-disabled-checkbox' + '.ppcp-disabled-checkbox' ) function atLeastOneChecked(checkboxesNodeList) { diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index e2616fca4..a96fb1582 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -712,7 +712,7 @@ return array( ), 'requirements' => array(), 'gateway' => array( 'paypal', 'dcc' ), - 'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppc-disabled-checkbox' ), + 'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ), ), 'logging_enabled' => array( 'title' => __( 'Logging', 'woocommerce-paypal-payments' ), From bb847b47f6f260371a15edae0134ec1b1efa29d4 Mon Sep 17 00:00:00 2001 From: Danae Millan Date: Tue, 30 Nov 2021 15:20:31 -0300 Subject: [PATCH 14/44] Rename function for getting the Vaulting setting description As suggested in the PR to be more aligned with the naming convention. --- modules/ppcp-wc-gateway/services.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index a96fb1582..6b2ba0467 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -895,7 +895,7 @@ return array( 'message_enabled' => array( 'title' => __( 'Enable message on Checkout', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => sprintf( $container->get( 'button.helper.pay-later-label' ), __( 'Enable on Checkout', 'woocommerce-paypal-payments' ) ), + 'label' => sprintf( $container->get( 'wcgateway.settings.fields.pay-later-label' ), __( 'Enable on Checkout', 'woocommerce-paypal-payments' ) ), 'default' => true, 'screens' => array( State::STATE_PROGRESSIVE, @@ -1198,7 +1198,7 @@ return array( 'message_product_enabled' => array( 'title' => __( 'Enable message on Single Product', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => sprintf( $container->get( 'button.helper.pay-later-label' ), __( 'Enable on Single Product', 'woocommerce-paypal-payments' ) ), + 'label' => sprintf( $container->get( 'wcgateway.settings.fields.pay-later-label' ), __( 'Enable on Single Product', 'woocommerce-paypal-payments' ) ), 'default' => true, 'screens' => array( State::STATE_PROGRESSIVE, @@ -1501,7 +1501,7 @@ return array( 'message_cart_enabled' => array( 'title' => __( 'Enable message on Cart', 'woocommerce-paypal-payments' ), 'type' => 'checkbox', - 'label' => sprintf( $container->get( 'button.helper.pay-later-label' ), __( 'Enable on Cart', 'woocommerce-paypal-payments' ) ), + 'label' => sprintf( $container->get( 'wcgateway.settings.fields.pay-later-label' ), __( 'Enable on Cart', 'woocommerce-paypal-payments' ) ), 'default' => true, 'screens' => array( State::STATE_PROGRESSIVE, @@ -2041,7 +2041,7 @@ return array( return $vaulting_label; }, - 'button.helper.pay-later-label' => static function ( ContainerInterface $container ): string { + 'wcgateway.settings.fields.pay-later-label' => static function ( ContainerInterface $container ): string { $pay_later_label = '%s'; $pay_later_label .= ''; $pay_later_label .= __( "You have PayPal vaulting enabled, that's why Pay Later Messaging options are unavailable now. You cannot use both features at the same time.", 'woocommerce-paypal-payments' ); From b0ad59e92f25e1dbc3f80ae87dec2bbc70d3a864 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 11 Apr 2022 18:29:30 +0400 Subject: [PATCH 15/44] Unset the gateway only on checkout page --- modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index 64520b38c..33fbcef6e 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -69,7 +69,7 @@ class DisableGateways { unset( $methods[ CreditCardGateway::ID ] ); } - if ( $this->settings->has( 'button_enabled' ) && ! $this->settings->get( 'button_enabled' ) && ! $this->session_handler->order() ) { + if ( $this->settings->has( 'button_enabled' ) && ! $this->settings->get( 'button_enabled' ) && ! $this->session_handler->order() && is_checkout() ) { unset( $methods[ PayPalGateway::ID ] ); } From de855042a000299c0fac612bfa94a5c4c71894bf Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 20 Apr 2022 19:51:26 +0400 Subject: [PATCH 16/44] Add "PayPal Transaction Key" into order custom fields. --- modules/ppcp-wc-gateway/src/WCGatewayModule.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 8ec2f9965..44427210a 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -81,6 +81,10 @@ class WCGatewayModule implements ModuleInterface { if ( $breakdown ) { $wc_order->update_meta_data( PayPalGateway::FEES_META_KEY, $breakdown->to_array() ); $wc_order->save_meta_data(); + $paypal_fee = $breakdown->paypal_fee(); + if ( $paypal_fee ) { + update_post_meta( $wc_order->get_id(), 'PayPal Transaction Key', $paypal_fee->value() ); + } } }, 10, From 4c1695788fc0d6f27049848da417b716dd17fe66 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 25 Apr 2022 19:14:21 +0400 Subject: [PATCH 17/44] Create Fraud processor response and it's factory. --- .../src/Entity/FraudProcessorResponse.php | 74 +++++++++++++++++++ .../Factory/FraudProcessorResponseFactory.php | 33 +++++++++ 2 files changed, 107 insertions(+) create mode 100644 modules/ppcp-api-client/src/Entity/FraudProcessorResponse.php create mode 100644 modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php diff --git a/modules/ppcp-api-client/src/Entity/FraudProcessorResponse.php b/modules/ppcp-api-client/src/Entity/FraudProcessorResponse.php new file mode 100644 index 000000000..2cc7c5480 --- /dev/null +++ b/modules/ppcp-api-client/src/Entity/FraudProcessorResponse.php @@ -0,0 +1,74 @@ +avs_code = $avs_code; + $this->cvv_code = $cvv_code; + } + + /** + * Returns the AVS response code. + * + * @return string|null + */ + public function avs_code(): ?string { + return $this->avs_code; + } + + /** + * Returns the CVV response code. + * + * @return string|null + */ + public function cvv_code(): ?string { + return $this->cvv_code; + } + + /** + * Returns the object as array. + * + * @return array + */ + public function to_array(): array { + return array( + 'avs_code' => $this->avs_code() ?: '', + 'address_match' => $this->avs_code() === 'M' ? 'Y' : 'N', + 'postal_match' => $this->avs_code() === 'M' ? 'Y' : 'N', + 'cvv_match' => $this->cvv_code() === 'M' ? 'Y' : 'N', + ); + } + +} diff --git a/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php b/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php new file mode 100644 index 000000000..e48d7fd3e --- /dev/null +++ b/modules/ppcp-api-client/src/Factory/FraudProcessorResponseFactory.php @@ -0,0 +1,33 @@ +avs_code ?: null; + $cvv_code = $data->cvv_code ?: null; + + return new FraudProcessorResponse( $avs_code, $cvv_code ); + } +} From fd77690d467a12eb2692815e1d5f277670c38243 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 25 Apr 2022 19:16:09 +0400 Subject: [PATCH 18/44] Include fraud response in order capture. --- .../ppcp-api-client/src/Entity/Capture.php | 24 ++++++++++++++++++- .../src/Factory/CaptureFactory.php | 20 +++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-api-client/src/Entity/Capture.php b/modules/ppcp-api-client/src/Entity/Capture.php index 2c6d55fbc..a462f6998 100644 --- a/modules/ppcp-api-client/src/Entity/Capture.php +++ b/modules/ppcp-api-client/src/Entity/Capture.php @@ -58,6 +58,13 @@ class Capture { */ private $seller_receivable_breakdown; + /** + * The fraud processor response (AVS, CVV ...). + * + * @var FraudProcessorResponse|null + */ + protected $fraud_processor_response; + /** * The invoice id. * @@ -83,6 +90,7 @@ class Capture { * @param string $invoice_id The invoice id. * @param string $custom_id The custom id. * @param SellerReceivableBreakdown|null $seller_receivable_breakdown The detailed breakdown of the capture activity (fees, ...). + * @param FraudProcessorResponse|null $fraud_processor_response The fraud processor response (AVS, CVV ...). */ public function __construct( string $id, @@ -92,7 +100,8 @@ class Capture { string $seller_protection, string $invoice_id, string $custom_id, - ?SellerReceivableBreakdown $seller_receivable_breakdown + ?SellerReceivableBreakdown $seller_receivable_breakdown, + ?FraudProcessorResponse $fraud_processor_response ) { $this->id = $id; @@ -103,6 +112,7 @@ class Capture { $this->invoice_id = $invoice_id; $this->custom_id = $custom_id; $this->seller_receivable_breakdown = $seller_receivable_breakdown; + $this->fraud_processor_response = $fraud_processor_response; } /** @@ -177,6 +187,15 @@ class Capture { return $this->seller_receivable_breakdown; } + /** + * Returns the fraud processor response (AVS, CVV ...). + * + * @return FraudProcessorResponse|null + */ + public function fraud_processor_response() : ?FraudProcessorResponse { + return $this->fraud_processor_response; + } + /** * Returns the entity as array. * @@ -199,6 +218,9 @@ class Capture { if ( $this->seller_receivable_breakdown ) { $data['seller_receivable_breakdown'] = $this->seller_receivable_breakdown->to_array(); } + if ( $this->fraud_processor_response ) { + $data['fraud_processor_response'] = $this->fraud_processor_response->to_array(); + } return $data; } } diff --git a/modules/ppcp-api-client/src/Factory/CaptureFactory.php b/modules/ppcp-api-client/src/Factory/CaptureFactory.php index 7b3b5d82c..0c833fca6 100644 --- a/modules/ppcp-api-client/src/Factory/CaptureFactory.php +++ b/modules/ppcp-api-client/src/Factory/CaptureFactory.php @@ -32,19 +32,29 @@ class CaptureFactory { */ private $seller_receivable_breakdown_factory; + /** + * The FraudProcessorResponseFactory factory. + * + * @var FraudProcessorResponseFactory + */ + protected $fraud_processor_response_factory; + /** * CaptureFactory constructor. * * @param AmountFactory $amount_factory The amount factory. * @param SellerReceivableBreakdownFactory $seller_receivable_breakdown_factory The SellerReceivableBreakdown factory. + * @param FraudProcessorResponseFactory $fraud_processor_response_factory The FraudProcessorResponseFactory factory. */ public function __construct( AmountFactory $amount_factory, - SellerReceivableBreakdownFactory $seller_receivable_breakdown_factory + SellerReceivableBreakdownFactory $seller_receivable_breakdown_factory, + FraudProcessorResponseFactory $fraud_processor_response_factory ) { $this->amount_factory = $amount_factory; $this->seller_receivable_breakdown_factory = $seller_receivable_breakdown_factory; + $this->fraud_processor_response_factory = $fraud_processor_response_factory; } /** @@ -55,12 +65,15 @@ class CaptureFactory { * @return Capture */ public function from_paypal_response( \stdClass $data ) : Capture { - $reason = $data->status_details->reason ?? null; $seller_receivable_breakdown = isset( $data->seller_receivable_breakdown ) ? $this->seller_receivable_breakdown_factory->from_paypal_response( $data->seller_receivable_breakdown ) : null; + $fraud_processor_response = isset( $data->processor_response ) ? + $this->fraud_processor_response_factory->from_paypal_response( $data->processor_response ) + : null; + return new Capture( (string) $data->id, new CaptureStatus( @@ -72,7 +85,8 @@ class CaptureFactory { (string) $data->seller_protection->status, (string) $data->invoice_id, (string) $data->custom_id, - $seller_receivable_breakdown + $seller_receivable_breakdown, + $fraud_processor_response ); } } From d148a1bf9a9b5fc55456f0487324414e215fb8ea Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 25 Apr 2022 19:17:13 +0400 Subject: [PATCH 19/44] Adjust API client services to support fraud response. --- modules/ppcp-api-client/services.php | 7 +++- .../ppcp-wc-gateway/src/WCGatewayModule.php | 38 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index a8226819f..24bcd1a21 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -27,6 +27,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\ApplicationContextFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\AuthorizationFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ExchangeRateFactory; +use WooCommerce\PayPalCommerce\ApiClient\Factory\FraudProcessorResponseFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ItemFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\MoneyFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory; @@ -257,7 +258,8 @@ return array( $amount_factory = $container->get( 'api.factory.amount' ); return new CaptureFactory( $amount_factory, - $container->get( 'api.factory.seller-receivable-breakdown' ) + $container->get( 'api.factory.seller-receivable-breakdown' ), + $container->get( 'api.factory.fraud-processor-response' ) ); }, 'api.factory.purchase-unit' => static function ( ContainerInterface $container ): PurchaseUnitFactory { @@ -354,6 +356,9 @@ return array( $container->get( 'api.factory.platform-fee' ) ); }, + 'api.factory.fraud-processor-response' => static function ( ContainerInterface $container ): FraudProcessorResponseFactory { + return new FraudProcessorResponseFactory(); + }, 'api.helpers.dccapplies' => static function ( ContainerInterface $container ) : DccApplies { return new DccApplies( $container->get( 'api.dcc-supported-country-currency-matrix' ), diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 8ec2f9965..312345a1d 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -82,6 +82,44 @@ class WCGatewayModule implements ModuleInterface { $wc_order->update_meta_data( PayPalGateway::FEES_META_KEY, $breakdown->to_array() ); $wc_order->save_meta_data(); } + + $fraud = $capture->fraud_processor_response(); + if ( $fraud ) { + $fraud_responses = $fraud->to_array(); + $avs_response_order_note_title = __( 'Address Verification Result', 'woocommerce-paypal-payments' ); + /* translators: %1$s is AVS order note title, %2$s is AVS order note result markup */ + $avs_response_order_note_format = __( '%1$s %2$s', 'woocommerce-paypal-payments' ); + $avs_response_order_note_result_format = '
    +
  • %1$s
  • +
      +
    • %2$s
    • +
    • %3$s
    • +
    +
'; + $avs_response_order_note_result = sprintf( + $avs_response_order_note_result_format, + /* translators: %s is fraud AVS code */ + sprintf( __( 'AVS: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['avs_code'] ) ), + /* translators: %s is fraud AVS address match */ + sprintf( __( 'Address Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['address_match'] ) ), + /* translators: %s is fraud AVS postal match */ + sprintf( __( 'Postal Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['postal_match'] ) ) + ); + $avs_response_order_note = sprintf( + $avs_response_order_note_format, + esc_html( $avs_response_order_note_title ), + wp_kses_post( $avs_response_order_note_result ) + ); + $wc_order->add_order_note( $avs_response_order_note ); + + $cvv_response_order_note_format = '
  • %1$s
'; + $cvv_response_order_note = sprintf( + $cvv_response_order_note_format, + /* translators: %s is fraud CVV match */ + sprintf( __( 'CVV2 Match: %s', 'woocommerce-paypal-payments' ), esc_html( $fraud_responses['cvv_match'] ) ) + ); + $wc_order->add_order_note( $cvv_response_order_note ); + } }, 10, 2 From 13e04b009c814649ab051c5598850d5a225ad1f2 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 27 Apr 2022 23:50:23 +0300 Subject: [PATCH 20/44] Fix const name --- .../ppcp-api-client/src/Factory/AmountFactory.php | 2 +- modules/ppcp-vaulting/src/PaymentTokenChecker.php | 2 +- .../ppcp-wc-gateway/src/Gateway/PayPalGateway.php | 12 ++++++------ .../ppcp-wc-gateway/src/Processor/OrderMetaTrait.php | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-api-client/src/Factory/AmountFactory.php b/modules/ppcp-api-client/src/Factory/AmountFactory.php index bf9637c32..d0079f1a9 100644 --- a/modules/ppcp-api-client/src/Factory/AmountFactory.php +++ b/modules/ppcp-api-client/src/Factory/AmountFactory.php @@ -127,7 +127,7 @@ class AmountFactory { $total_value = (float) $order->get_total(); if ( ( CreditCardGateway::ID === $order->get_payment_method() - || ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE ) ) + || ( PayPalGateway::ID === $order->get_payment_method() && 'card' === $order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) ) && $this->is_free_trial_order( $order ) ) { diff --git a/modules/ppcp-vaulting/src/PaymentTokenChecker.php b/modules/ppcp-vaulting/src/PaymentTokenChecker.php index 45cdae20c..604c7cc37 100644 --- a/modules/ppcp-vaulting/src/PaymentTokenChecker.php +++ b/modules/ppcp-vaulting/src/PaymentTokenChecker.php @@ -119,7 +119,7 @@ class PaymentTokenChecker { try { if ( $this->is_free_trial_order( $wc_order ) ) { if ( CreditCardGateway::ID === $wc_order->get_payment_method() - || ( PayPalGateway::ID === $wc_order->get_payment_method() && 'card' === $wc_order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE ) ) + || ( PayPalGateway::ID === $wc_order->get_payment_method() && 'card' === $wc_order->get_meta( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY ) ) ) { $order = $this->order_repository->for_wc_order( $wc_order ); $this->authorized_payments_processor->void_authorizations( $order ); diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index 5c959d388..d3ddf21f8 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -33,12 +33,12 @@ class PayPalGateway extends \WC_Payment_Gateway { use ProcessPaymentTrait; - const ID = 'ppcp-gateway'; - const INTENT_META_KEY = '_ppcp_paypal_intent'; - const ORDER_ID_META_KEY = '_ppcp_paypal_order_id'; - const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode'; - const ORDER_PAYMENT_SOURCE = '_ppcp_paypal_payment_source'; - const FEES_META_KEY = '_ppcp_paypal_fees'; + const ID = 'ppcp-gateway'; + const INTENT_META_KEY = '_ppcp_paypal_intent'; + const ORDER_ID_META_KEY = '_ppcp_paypal_order_id'; + const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode'; + const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source'; + const FEES_META_KEY = '_ppcp_paypal_fees'; /** * The Settings Renderer. diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php index 4aad635a4..3a9049bbd 100644 --- a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php +++ b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php @@ -39,7 +39,7 @@ trait OrderMetaTrait { ); $payment_source = $this->get_payment_source( $order ); if ( $payment_source ) { - $wc_order->update_meta_data( PayPalGateway::ORDER_PAYMENT_SOURCE, $payment_source ); + $wc_order->update_meta_data( PayPalGateway::ORDER_PAYMENT_SOURCE_META_KEY, $payment_source ); } } From 35f058870ad87fe5010e07af22485381553f4443 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Apr 2022 16:20:05 +0300 Subject: [PATCH 21/44] Skip already handled refunds in webhook When we add a refund from WC, we still receive a webhook with it. And for some partial refunds the duplicated refund can be added in WC. --- .../src/Endpoint/PaymentsEndpoint.php | 9 ++-- .../src/Gateway/PayPalGateway.php | 1 + .../src/Processor/RefundMetaTrait.php | 47 +++++++++++++++++++ .../src/Processor/RefundProcessor.php | 10 ++-- .../src/Handler/PaymentCaptureRefunded.php | 17 +++++-- 5 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 modules/ppcp-wc-gateway/src/Processor/RefundMetaTrait.php diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php index e470dba46..a04ddbb41 100644 --- a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php @@ -198,11 +198,11 @@ class PaymentsEndpoint { * * @param Refund $refund The refund to be processed. * - * @return void + * @return string Refund ID. * @throws RuntimeException If the request fails. * @throws PayPalApiException If the request fails. */ - public function refund( Refund $refund ) : void { + public function refund( Refund $refund ) : string { $bearer = $this->bearer->bearer(); $url = trailingslashit( $this->host ) . 'v2/payments/captures/' . $refund->for_capture()->id() . '/refund'; $args = array( @@ -223,12 +223,15 @@ class PaymentsEndpoint { } $status_code = (int) wp_remote_retrieve_response_code( $response ); - if ( 201 !== $status_code ) { + $json = json_decode( $response['body'] ); + if ( 201 !== $status_code || ! is_object( $json ) ) { throw new PayPalApiException( $json, $status_code ); } + + return $json->id; } /** diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php index d3ddf21f8..9770f7c28 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php @@ -39,6 +39,7 @@ class PayPalGateway extends \WC_Payment_Gateway { const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode'; const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source'; const FEES_META_KEY = '_ppcp_paypal_fees'; + const REFUNDS_META_KEY = '_ppcp_refunds'; /** * The Settings Renderer. diff --git a/modules/ppcp-wc-gateway/src/Processor/RefundMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/RefundMetaTrait.php new file mode 100644 index 000000000..c847bc7a7 --- /dev/null +++ b/modules/ppcp-wc-gateway/src/Processor/RefundMetaTrait.php @@ -0,0 +1,47 @@ +get_refunds_meta( $wc_order ); + $refunds[] = $refund_id; + $wc_order->update_meta_data( PayPalGateway::REFUNDS_META_KEY, $refunds ); + $wc_order->save(); + } + + /** + * Returns refund IDs from the order metadata. + * + * @param WC_Order $wc_order The WC order. + * + * @return string[] + */ + protected function get_refunds_meta( WC_Order $wc_order ): array { + $refunds = $wc_order->get_meta( PayPalGateway::REFUNDS_META_KEY ); + if ( ! is_array( $refunds ) ) { + $refunds = array(); + } + return $refunds; + } +} diff --git a/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php b/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php index 1d588124c..16f9b4698 100644 --- a/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php +++ b/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php @@ -26,6 +26,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; * Class RefundProcessor */ class RefundProcessor { + use RefundMetaTrait; private const REFUND_MODE_REFUND = 'refund'; private const REFUND_MODE_VOID = 'void'; @@ -113,8 +114,8 @@ class RefundProcessor { throw new RuntimeException( 'No capture.' ); } - $capture = $captures[0]; - $refund = new Refund( + $capture = $captures[0]; + $refund = new Refund( $capture, $capture->invoice_id(), $reason, @@ -122,7 +123,10 @@ class RefundProcessor { new Money( $amount, $wc_order->get_currency() ) ) ); - $this->payments_endpoint->refund( $refund ); + $refund_id = $this->payments_endpoint->refund( $refund ); + + $this->add_refund_to_meta( $wc_order, $refund_id ); + break; case self::REFUND_MODE_VOID: $voidable_authorizations = array_filter( diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php index ed85bc040..13c8eb36e 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks\Handler; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; /** @@ -17,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; */ class PaymentCaptureRefunded implements RequestHandler { - use PrefixTrait, TransactionIdHandlingTrait; + use PrefixTrait, TransactionIdHandlingTrait, RefundMetaTrait; /** * The logger. @@ -68,6 +69,7 @@ class PaymentCaptureRefunded implements RequestHandler { $response = array( 'success' => false ); $order_id = isset( $request['resource']['custom_id'] ) ? $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0; + $refund_id = (string) ( $request['resource']['id'] ?? '' ); if ( ! $order_id ) { $message = sprintf( // translators: %s is the PayPal webhook Id. @@ -93,7 +95,7 @@ class PaymentCaptureRefunded implements RequestHandler { $message = sprintf( // translators: %s is the PayPal refund Id. __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ), - isset( $request['resource']['id'] ) ? $request['resource']['id'] : '' + $refund_id ); $this->logger->log( 'warning', @@ -106,6 +108,12 @@ class PaymentCaptureRefunded implements RequestHandler { return rest_ensure_response( $response ); } + $already_added_refunds = $this->get_refunds_meta( $wc_order ); + if ( in_array( $refund_id, $already_added_refunds, true ) ) { + $this->logger->info( "Refund {$refund_id} is already handled." ); + return new WP_REST_Response( $response ); + } + /** * The WooCommerce order. * @@ -152,8 +160,9 @@ class PaymentCaptureRefunded implements RequestHandler { ) ); - if ( is_array( $request['resource'] ) && isset( $request['resource']['id'] ) ) { - $this->update_transaction_id( $request['resource']['id'], $wc_order, $this->logger ); + if ( $refund_id ) { + $this->update_transaction_id( $refund_id, $wc_order, $this->logger ); + $this->add_refund_to_meta( $wc_order, $refund_id ); } $response['success'] = true; From 93ef548b65ae25d00fefb83ca39c053fdeb664e5 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 28 Apr 2022 16:20:30 +0300 Subject: [PATCH 22/44] Fix psalm errors --- .../src/Handler/PaymentCaptureRefunded.php | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php index 13c8eb36e..7a4db8bf1 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php @@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\Webhooks\Handler; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundMetaTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; +use WP_REST_Request; +use WP_REST_Response; /** * Class PaymentCaptureRefunded @@ -50,24 +52,24 @@ class PaymentCaptureRefunded implements RequestHandler { /** * Whether a handler is responsible for a given request or not. * - * @param \WP_REST_Request $request The request. + * @param WP_REST_Request $request The request. * * @return bool */ - public function responsible_for_request( \WP_REST_Request $request ): bool { + public function responsible_for_request( WP_REST_Request $request ): bool { return in_array( $request['event_type'], $this->event_types(), true ); } /** * Responsible for handling the request. * - * @param \WP_REST_Request $request The request. + * @param WP_REST_Request $request The request. * - * @return \WP_REST_Response + * @return WP_REST_Response */ - public function handle_request( \WP_REST_Request $request ): \WP_REST_Response { - $response = array( 'success' => false ); - $order_id = isset( $request['resource']['custom_id'] ) ? + public function handle_request( WP_REST_Request $request ): WP_REST_Response { + $response = array( 'success' => false ); + $order_id = isset( $request['resource']['custom_id'] ) ? $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0; $refund_id = (string) ( $request['resource']['id'] ?? '' ); if ( ! $order_id ) { @@ -87,7 +89,7 @@ class PaymentCaptureRefunded implements RequestHandler { ) ); $response['message'] = $message; - return rest_ensure_response( $response ); + return new WP_REST_Response( $response ); } $wc_order = wc_get_order( $order_id ); @@ -105,7 +107,7 @@ class PaymentCaptureRefunded implements RequestHandler { ) ); $response['message'] = $message; - return rest_ensure_response( $response ); + return new WP_REST_Response( $response ); } $already_added_refunds = $this->get_refunds_meta( $wc_order ); @@ -140,7 +142,7 @@ class PaymentCaptureRefunded implements RequestHandler { ); $response['message'] = $refund->get_error_message(); - return rest_ensure_response( $response ); + return new WP_REST_Response( $response ); } $this->logger->log( @@ -166,6 +168,6 @@ class PaymentCaptureRefunded implements RequestHandler { } $response['success'] = true; - return rest_ensure_response( $response ); + return new WP_REST_Response( $response ); } } From c4f5d7a139d7e871f9bb048667dd6e1df3554d9d Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 2 May 2022 17:48:14 +0400 Subject: [PATCH 23/44] Move the subscription cart check into the callbacks. The subscription product in cart existence and the vaulting enabled check should be called inside the button renderer hook callback so that the mini-cart ajax refresh will take it into the consideration. --- .../ppcp-button/src/Assets/SmartButton.php | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 1161f75d1..44fab492a 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -220,16 +220,15 @@ class SmartButton implements SmartButtonInterface { * @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found. */ public function render_wrapper(): bool { - - if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { - return false; - } - if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) { $this->render_button_wrapper_registrar(); $this->render_message_wrapper_registrar(); } + if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { + return false; + } + if ( $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) @@ -432,6 +431,10 @@ class SmartButton implements SmartButtonInterface { add_action( $this->mini_cart_button_renderer_hook(), function () { + if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { + return; + } + if ( $this->is_cart_price_total_zero() || $this->is_free_trial_cart() ) { return; } @@ -521,6 +524,11 @@ class SmartButton implements SmartButtonInterface { * Renders the HTML for the buttons. */ public function button_renderer() { + + if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { + return; + } + $product = wc_get_product(); if ( ! is_checkout() && is_a( $product, \WC_Product::class ) @@ -548,6 +556,9 @@ class SmartButton implements SmartButtonInterface { * Renders the HTML for the credit messaging. */ public function message_renderer() { + if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) { + return false; + } echo '
'; } From dd4b39971f83e87a54e3522465a2de2f1f8b2eba Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 3 May 2022 16:14:22 +0300 Subject: [PATCH 24/44] Remove duplicated variable --- modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php index a04ddbb41..2a39a4b9b 100644 --- a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php @@ -216,7 +216,6 @@ class PaymentsEndpoint { ); $response = $this->request( $url, $args ); - $json = json_decode( $response['body'] ); if ( is_wp_error( $response ) ) { throw new RuntimeException( 'Could not refund payment.' ); From df6e784d29078169e964acb8c49bf218c38536e2 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 5 May 2022 15:50:21 +0300 Subject: [PATCH 25/44] Add DDEV --- .ddev/commands/web/orchestrate | 42 ++++ .../orchestrate.d/00_download_wordpress.sh | 6 + .../web/orchestrate.d/01_gitignore.sh | 6 + .../web/orchestrate.d/05_wp_config.sh | 22 ++ .../web/orchestrate.d/10_wp_install.sh | 15 ++ .../20_cleanup_default_plugins.sh | 4 + .../30_install_plugin_packages.sh | 6 + .../orchestrate.d/40_setup_and_activate.sh | 5 + .../web/orchestrate.d/50_woocommerce.sh | 3 + .../web/orchestrate.d/60_flush_rewrites.sh | 9 + .ddev/commands/web/orchestrate.d/70_themes.sh | 3 + .ddev/config.yaml | 204 ++++++++++++++++++ .ddev/docker-compose.wp-plugin.yaml | 5 + .ddev/homeadditions/.bashrc | 39 ++++ 14 files changed, 369 insertions(+) create mode 100755 .ddev/commands/web/orchestrate create mode 100644 .ddev/commands/web/orchestrate.d/00_download_wordpress.sh create mode 100644 .ddev/commands/web/orchestrate.d/01_gitignore.sh create mode 100644 .ddev/commands/web/orchestrate.d/05_wp_config.sh create mode 100644 .ddev/commands/web/orchestrate.d/10_wp_install.sh create mode 100644 .ddev/commands/web/orchestrate.d/20_cleanup_default_plugins.sh create mode 100644 .ddev/commands/web/orchestrate.d/30_install_plugin_packages.sh create mode 100644 .ddev/commands/web/orchestrate.d/40_setup_and_activate.sh create mode 100644 .ddev/commands/web/orchestrate.d/50_woocommerce.sh create mode 100644 .ddev/commands/web/orchestrate.d/60_flush_rewrites.sh create mode 100644 .ddev/commands/web/orchestrate.d/70_themes.sh create mode 100644 .ddev/config.yaml create mode 100644 .ddev/docker-compose.wp-plugin.yaml create mode 100644 .ddev/homeadditions/.bashrc diff --git a/.ddev/commands/web/orchestrate b/.ddev/commands/web/orchestrate new file mode 100755 index 000000000..55599bfb6 --- /dev/null +++ b/.ddev/commands/web/orchestrate @@ -0,0 +1,42 @@ +#!/bin/bash +set -x +## Description: Set up the development environment +## Usage: orchestrate +## Example: "ddev orchestrate" + +mkdir -p "${DDEV_DOCROOT}" +pushd "${DDEV_DOCROOT}" +PLUGIN_FOLDER="${DDEV_DOCROOT}/wp-content/plugins/${PLUGIN_NAME:-$DDEV_PROJECT}" +VALID_ARGS=$(getopt -o fp: --long force,plugin: -- "$@") +if [[ $? -ne 0 ]]; then + exit 1; +fi + +eval set -- "$VALID_ARGS" +while [ : ]; do + case "$1" in + -f | --force) + echo "Removing WordPress installation" + shift + export RECREATE_ENV=1; + popd + find "${DDEV_DOCROOT}" -mindepth 1 ! -regex "^${PLUGIN_FOLDER}\(/.*\)?" -delete + pushd "${DDEV_DOCROOT}" + ;; + -p | --plugin) + echo "Processing 'plugin' option. Input argument is '$2'" + shift 2 + ;; + --) shift; + break + ;; + esac +done + +# Execute all fragments from orchestrate.d +if [ -d "${0}.d" ]; then + for FN in ${0}.d/*.sh ; do + echo $FN + source "${FN}" + done +fi diff --git a/.ddev/commands/web/orchestrate.d/00_download_wordpress.sh b/.ddev/commands/web/orchestrate.d/00_download_wordpress.sh new file mode 100644 index 000000000..6ff769fd3 --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/00_download_wordpress.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +if ! wp core download; then + echo 'WordPress is already installed.' + exit +fi diff --git a/.ddev/commands/web/orchestrate.d/01_gitignore.sh b/.ddev/commands/web/orchestrate.d/01_gitignore.sh new file mode 100644 index 000000000..ed17388fd --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/01_gitignore.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +cat << 'EOF' > ".gitignore" +# ignores everything in the docroot (docroot in config.yaml), this path may not be included in the standard .ddev/.gitignore. +* +EOF diff --git a/.ddev/commands/web/orchestrate.d/05_wp_config.sh b/.ddev/commands/web/orchestrate.d/05_wp_config.sh new file mode 100644 index 000000000..f6a80ff15 --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/05_wp_config.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# Create wp-config.php +# the PHP snippet avoids some notices during CLI calls +# https://make.wordpress.org/cli/handbook/guides/common-issues/#php-notice-undefined-index-on-_server-superglobal +printf ' +if ( defined( "WP_CLI" ) && WP_CLI && ! isset( $_SERVER["HTTP_HOST"] ) ) { + $_SERVER["HTTP_HOST"] = "%s"; +} +' "${DDEV_HOSTNAME}" | wp config create \ + --dbname=db \ + --dbuser=db \ + --dbpass=db \ + --dbhost=db \ + --force \ + --extra-php + +wp config set WP_DEBUG true --raw +wp config set WP_DEBUG_DISPLAY true --raw +wp config set WP_DEBUG_LOG true --raw +wp config set WP_SITEURL '(isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] ? "https://" : "http://") . $_SERVER["HTTP_HOST"]' --raw +wp config set WP_HOME '(isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] ? "https://" : "http://") . $_SERVER["HTTP_HOST"]' --raw diff --git a/.ddev/commands/web/orchestrate.d/10_wp_install.sh b/.ddev/commands/web/orchestrate.d/10_wp_install.sh new file mode 100644 index 000000000..4362959b1 --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/10_wp_install.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +if [ ! -z "${RECREATE_ENV}" ]; then + echo "Deleting database before creating a new one" + wp db clean --yes +fi + +wp core install \ + --title="${WP_TITLE}" \ + --admin_user="${ADMIN_USER}" \ + --admin_password="${ADMIN_PASS}" \ + --url="${DDEV_PRIMARY_URL}" \ + --admin_email="${ADMIN_EMAIL}" \ + --skip-email + diff --git a/.ddev/commands/web/orchestrate.d/20_cleanup_default_plugins.sh b/.ddev/commands/web/orchestrate.d/20_cleanup_default_plugins.sh new file mode 100644 index 000000000..82e06dc8d --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/20_cleanup_default_plugins.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +wp plugin is-installed akismet && wp plugin uninstall akismet +wp plugin is-installed hello && wp plugin uninstall hello diff --git a/.ddev/commands/web/orchestrate.d/30_install_plugin_packages.sh b/.ddev/commands/web/orchestrate.d/30_install_plugin_packages.sh new file mode 100644 index 000000000..91cb0f479 --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/30_install_plugin_packages.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +popd + +composer install +yarn run build:dev diff --git a/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh b/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh new file mode 100644 index 000000000..fdd06dbfc --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +pushd "${DDEV_DOCROOT}" + +wp plugin activate "${PLUGIN_NAME:-$DDEV_PROJECT}" diff --git a/.ddev/commands/web/orchestrate.d/50_woocommerce.sh b/.ddev/commands/web/orchestrate.d/50_woocommerce.sh new file mode 100644 index 000000000..8d6ab3e0a --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/50_woocommerce.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +wp plugin install woocommerce --version="${WC_VERSION}" --activate diff --git a/.ddev/commands/web/orchestrate.d/60_flush_rewrites.sh b/.ddev/commands/web/orchestrate.d/60_flush_rewrites.sh new file mode 100644 index 000000000..32fdc1064 --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/60_flush_rewrites.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Needed for generating the .htaccess file +echo "apache_modules: + - mod_rewrite +" > wp-cli.yml + +wp rewrite structure '/%postname%' +wp rewrite flush --hard diff --git a/.ddev/commands/web/orchestrate.d/70_themes.sh b/.ddev/commands/web/orchestrate.d/70_themes.sh new file mode 100644 index 000000000..7f9a38ee4 --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/70_themes.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +wp theme install storefront --activate diff --git a/.ddev/config.yaml b/.ddev/config.yaml new file mode 100644 index 000000000..61712095c --- /dev/null +++ b/.ddev/config.yaml @@ -0,0 +1,204 @@ +name: woocommerce-paypal-payments +type: php +docroot: .ddev/wordpress +php_version: "7.1" +webserver_type: apache-fpm +router_http_port: "80" +router_https_port: "443" +xdebug_enabled: false +additional_hostnames: ['wc-pp'] +additional_fqdns: [] +mariadb_version: "10.3" +mysql_version: "" +nfs_mount_enabled: false +mutagen_enabled: false +use_dns_when_possible: true +composer_version: "1" +web_environment: + - WP_VERSION=5.9.3 + - WP_LOCALE=en_US + - WP_TITLE=WooCommerce PayPal Payments + - ADMIN_USER=admin + - ADMIN_PASS=admin + - ADMIN_EMAIL=admin@example.com + - WC_VERSION=6.1.0 + +# Key features of ddev's config.yaml: + +# name: # Name of the project, automatically provides +# http://projectname.ddev.site and https://projectname.ddev.site + +# type: # drupal6/7/8, backdrop, typo3, wordpress, php + +# docroot: # Relative path to the directory containing index.php. + +# php_version: "7.4" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1" + +# You can explicitly specify the webimage, dbimage, dbaimage lines but this +# is not recommended, as the images are often closely tied to ddev's' behavior, +# so this can break upgrades. + +# webimage: # nginx/php docker image. +# dbimage: # mariadb docker image. +# dbaimage: + +# mariadb_version and mysql_version +# ddev can use many versions of mariadb and mysql +# However these directives are mutually exclusive +# mariadb_version: 10.2 +# mysql_version: 8.0 + +# router_http_port: # Port to be used for http (defaults to port 80) +# router_https_port: # Port for https (defaults to 443) + +# xdebug_enabled: false # Set to true to enable xdebug and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better, +# as leaving xdebug enabled all the time is a big performance hit. + +# xhprof_enabled: false # Set to true to enable xhprof and "ddev start" or "ddev restart" +# Note that for most people the commands +# "ddev xhprof" to enable xhprof and "ddev xhprof off" to disable it work better, +# as leaving xhprof enabled all the time is a big performance hit. + +# webserver_type: nginx-fpm # or apache-fpm + +# timezone: Europe/Berlin +# This is the timezone used in the containers and by PHP; +# it can be set to any valid timezone, +# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# For example Europe/Dublin or MST7MDT + +# composer_version: "2" +# if composer_version:"2" it will use the most recent composer v2 +# It can also be set to "1", to get most recent composer v1 +# or "" for the default v2 created at release time. +# It can be set to any existing specific composer version. +# After first project 'ddev start' this will not be updated until it changes + +# additional_hostnames: +# - somename +# - someothername +# would provide http and https URLs for "somename.ddev.site" +# and "someothername.ddev.site". + +# additional_fqdns: +# - example.com +# - sub1.example.com +# would provide http and https URLs for "example.com" and "sub1.example.com" +# Please take care with this because it can cause great confusion. + +# upload_dir: custom/upload/dir +# would set the destination path for ddev import-files to /custom/upload/dir + +# working_dir: +# web: /var/www/html +# db: /home +# would set the default working directory for the web and db services. +# These values specify the destination directory for ddev ssh and the +# directory in which commands passed into ddev exec are run. + +# omit_containers: [db, dba, ddev-ssh-agent] +# Currently only these containers are supported. Some containers can also be +# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit +# the "db" container, several standard features of ddev that access the +# database container will be unusable. In the global configuration it is also +# possible to omit ddev-router, but not here. + +# nfs_mount_enabled: false +# Great performance improvement but requires host configuration first. +# See https://ddev.readthedocs.io/en/stable/users/performance/#using-nfs-to-mount-the-project-into-the-container + +# mutagen_enabled: false +# Experimental performance improvement using mutagen asynchronous updates. +# See https://ddev.readthedocs.io/en/latest/users/performance/#using-mutagen + +# fail_on_hook_fail: False +# Decide whether 'ddev start' should be interrupted by a failing hook + +# host_https_port: "59002" +# The host port binding for https can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_webserver_port: "59001" +# The host port binding for the ddev-webserver can be explicitly specified. It is +# dynamic unless otherwise specified. +# This is not used by most people, most people use the *router* instead +# of the localhost port. + +# host_db_port: "59002" +# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic +# unless explicitly specified. + +# phpmyadmin_port: "8036" +# phpmyadmin_https_port: "8037" +# The PHPMyAdmin ports can be changed from the default 8036 and 8037 + +# host_phpmyadmin_port: "8036" +# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be specified and bound. + +# mailhog_port: "8025" +# mailhog_https_port: "8026" +# The MailHog ports can be changed from the default 8025 and 8026 + +# host_mailhog_port: "8025" +# The mailhog port is not normally bound on the host at all, instead being routed +# through ddev-router, but it can be bound directly to localhost if specified here. + +# webimage_extra_packages: [php7.4-tidy, php-bcmath] +# Extra Debian packages that are needed in the webimage can be added here + +# dbimage_extra_packages: [telnet,netcat] +# Extra Debian packages that are needed in the dbimage can be added here + +# use_dns_when_possible: true +# If the host has internet access and the domain configured can +# successfully be looked up, DNS will be used for hostname resolution +# instead of editing /etc/hosts +# Defaults to true + +# project_tld: ddev.site +# The top-level domain used for project URLs +# The default "ddev.site" allows DNS lookup via a wildcard +# If you prefer you can change this to "ddev.local" to preserve +# pre-v1.9 behavior. + +# ngrok_args: --subdomain mysite --auth username:pass +# Provide extra flags to the "ngrok http" command, see +# https://ngrok.com/docs#http or run "ngrok http -h" + +# disable_settings_management: false +# If true, ddev will not create CMS-specific settings files like +# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php +# In this case the user must provide all such settings. + +# You can inject environment variables into the web container with: +# web_environment: +# - SOMEENV=somevalue +# - SOMEOTHERENV=someothervalue + +# no_project_mount: false +# (Experimental) If true, ddev will not mount the project into the web container; +# the user is responsible for mounting it manually or via a script. +# This is to enable experimentation with alternate file mounting strategies. +# For advanced users only! + +# bind_all_interfaces: false +# If true, host ports will be bound on all network interfaces, +# not just the localhost interface. This means that ports +# will be available on the local network if the host firewall +# allows it. + +# Many ddev commands can be extended to run tasks before or after the +# ddev command is executed, for example "post-start", "post-import-db", +# "pre-composer", "post-composer" +# See https://ddev.readthedocs.io/en/stable/users/extending-commands/ for more +# information on the commands that can be extended and the tasks you can define +# for them. Example: +#hooks: +# Un-comment to emit the WP CLI version after ddev start. +# post-start: +# - exec: wp cli version diff --git a/.ddev/docker-compose.wp-plugin.yaml b/.ddev/docker-compose.wp-plugin.yaml new file mode 100644 index 000000000..c2ed8d2e1 --- /dev/null +++ b/.ddev/docker-compose.wp-plugin.yaml @@ -0,0 +1,5 @@ +version: '3.6' +services: + web: + volumes: + - ../:/var/www/html/.ddev/wordpress/wp-content/plugins/${DDEV_PROJECT}:ro diff --git a/.ddev/homeadditions/.bashrc b/.ddev/homeadditions/.bashrc new file mode 100644 index 000000000..2b7447aec --- /dev/null +++ b/.ddev/homeadditions/.bashrc @@ -0,0 +1,39 @@ +source /usr/lib/git-core/git-sh-prompt + +# ignore duplicate commands, ignore commands starting with a space +# keep the last 5000 entries +export HISTCONTROL=erasedups +export HISTSIZE=5000 + +# Add global and "project root level" composer executables to the PATH +export PATH="$HOME/.composer/vendor/bin:$PATH" +export PATH="/var/www/html/vendor/bin:$PATH" + +# append to the history instead of overwriting (good for multiple connections) +shopt -s histappend + +export PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$\[$(tput sgr0)\] " + +export PHP_IDE_CONFIG='serverName=etrapi.localhost' +#export XDEBUG_CONFIG="mode=debug client_host=127.0.0.1 client_port=9003 start_with_request=yes" + +alias ls='ls --group-directories-first' +alias cp='cp -aiv' +alias grep='grep --color=always' +alias tgz='tar -pczf' + +profile(){ + XDEBUG_MODE=profile "$1" +} + +# all files in homeadditions/bashrc.d/*.sh will be +# interpreted as shell script so you can use these to +# customize your bash +if [ -d "${HOME}/bashrc.d" ]; then + for FN in ${HOME}/bashrc.d/*.sh ; do + source "${FN}" + done +fi + +cd "${DDEV_DOCROOT}" + From 6dc0a995f64c99d0a51d2ef356557c01b1a803d6 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 5 May 2022 18:37:46 +0300 Subject: [PATCH 26/44] Add ddev ngrok wrapper --- .ddev/bin/share | 28 ++++++++++++++++++++++++++++ .ddev/commands/web/wp | 7 +++++++ 2 files changed, 35 insertions(+) create mode 100755 .ddev/bin/share create mode 100755 .ddev/commands/web/wp diff --git a/.ddev/bin/share b/.ddev/bin/share new file mode 100755 index 000000000..a587784b2 --- /dev/null +++ b/.ddev/bin/share @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + +PROJECT_HOST=$(ddev wp option get home | tr -d '\r' | awk -F'^http[s]?://' '{print $2}') +ddev share >/dev/null & +NGROK_PID=$! +ngrok-url(){ + curl -s localhost:4040/api/tunnels | jq -r .tunnels[0].public_url | awk -F'^http[s]?://' '{print $2}' +} +wp-replace(){ + echo "Replacing ${1} with ${2}" + ddev wp search-replace "${1}" "${2}" --all-tables --precise --recurse-objects +} +kill-ngrok(){ + echo "Killing ngrok process ${NGROK_PID}" + pkill "${NGROK_PID}" + wp-replace "${NGROK_HOST}" "${PROJECT_HOST}" +} +echo "ngrok started with PID ${NGROK_PID}" + +echo "Waiting a few seconds for the service to come up" +sleep 3 +NGROK_HOST=$(ngrok-url) +wp-replace "${PROJECT_HOST}" "${NGROK_HOST}" + +echo "Your site is now reachable at https://${NGROK_HOST}" +echo "ctrl-c to stop sharing and revert database changes" +trap kill-ngrok INT +wait -f "${NGROK_PID}" diff --git a/.ddev/commands/web/wp b/.ddev/commands/web/wp new file mode 100755 index 000000000..27f2914b7 --- /dev/null +++ b/.ddev/commands/web/wp @@ -0,0 +1,7 @@ +#!/bin/bash +## Description: Run WordPress CLI inside the web container. DDEV core only provides this for the "wordpress" project type for some reason +## Usage: wp [flags] [args] +## Example: "ddev wp core version" or "ddev wp plugin install user-switching --activate" +## ProjectTypes: php + +wp "$@" --path=.ddev/wordpress From 074f1f68f7bfeff3ea885f5051e9408686d370c7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 6 May 2022 09:35:14 +0300 Subject: [PATCH 27/44] Support multisite in ddev --- .../web/orchestrate.d/10_wp_install.sh | 46 ++++++++++++++++--- .../orchestrate.d/40_setup_and_activate.sh | 7 ++- .../web/orchestrate.d/50_woocommerce.sh | 8 +++- .ddev/config.yaml | 2 + 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/.ddev/commands/web/orchestrate.d/10_wp_install.sh b/.ddev/commands/web/orchestrate.d/10_wp_install.sh index 4362959b1..417792bf3 100644 --- a/.ddev/commands/web/orchestrate.d/10_wp_install.sh +++ b/.ddev/commands/web/orchestrate.d/10_wp_install.sh @@ -5,11 +5,43 @@ if [ ! -z "${RECREATE_ENV}" ]; then wp db clean --yes fi -wp core install \ - --title="${WP_TITLE}" \ - --admin_user="${ADMIN_USER}" \ - --admin_password="${ADMIN_PASS}" \ - --url="${DDEV_PRIMARY_URL}" \ - --admin_email="${ADMIN_EMAIL}" \ - --skip-email +if [ "${WP_MULTISITE}" = "true" ]; then + wp core multisite-install \ + --title="${WP_TITLE}" \ + --admin_user="${ADMIN_USER}" \ + --admin_password="${ADMIN_PASS}" \ + --url="${DDEV_PRIMARY_URL}" \ + --admin_email="${ADMIN_EMAIL}" \ + --skip-email + cat << 'EOF' >> ".htaccess" +RewriteEngine On +RewriteBase / +RewriteRule ^index\.php$ - [L] + +RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L] + +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L] +RewriteRule . index.php [L] +EOF + +readarray -d , -t slugs <<< "${WP_MULTISITE_SLUGS},"; unset "slugs[-1]"; +for slug in "${slugs[@]}"; do + if [ ! -z "${slug}" ]; then + wp site create --slug="${slug}" + fi +done + +else + wp core install \ + --title="${WP_TITLE}" \ + --admin_user="${ADMIN_USER}" \ + --admin_password="${ADMIN_PASS}" \ + --url="${DDEV_PRIMARY_URL}" \ + --admin_email="${ADMIN_EMAIL}" \ + --skip-email +fi diff --git a/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh b/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh index fdd06dbfc..2762806a9 100644 --- a/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh +++ b/.ddev/commands/web/orchestrate.d/40_setup_and_activate.sh @@ -2,4 +2,9 @@ pushd "${DDEV_DOCROOT}" -wp plugin activate "${PLUGIN_NAME:-$DDEV_PROJECT}" +flags="" +if [ "${WP_MULTISITE}" = "true" ]; then + flags+=" --network" +fi + +wp plugin activate "${PLUGIN_NAME:-$DDEV_PROJECT}" $flags diff --git a/.ddev/commands/web/orchestrate.d/50_woocommerce.sh b/.ddev/commands/web/orchestrate.d/50_woocommerce.sh index 8d6ab3e0a..205d6a075 100644 --- a/.ddev/commands/web/orchestrate.d/50_woocommerce.sh +++ b/.ddev/commands/web/orchestrate.d/50_woocommerce.sh @@ -1,3 +1,9 @@ #!/bin/bash -wp plugin install woocommerce --version="${WC_VERSION}" --activate +flags="" +if [ "${WP_MULTISITE}" = "true" ]; then + flags+=" --network" +fi + +wp plugin install woocommerce --version="${WC_VERSION}" +wp plugin activate woocommerce $flags diff --git a/.ddev/config.yaml b/.ddev/config.yaml index 61712095c..c0e154f9a 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -18,6 +18,8 @@ web_environment: - WP_VERSION=5.9.3 - WP_LOCALE=en_US - WP_TITLE=WooCommerce PayPal Payments + - WP_MULTISITE=true + - WP_MULTISITE_SLUGS=de,es - ADMIN_USER=admin - ADMIN_PASS=admin - ADMIN_EMAIL=admin@example.com From 29aff60d27d8e61787df6e4995b63f99a5b0f206 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 6 May 2022 10:19:42 +0300 Subject: [PATCH 28/44] Fix PHP_IDE_CONFIG --- .ddev/homeadditions/.bashrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ddev/homeadditions/.bashrc b/.ddev/homeadditions/.bashrc index 2b7447aec..fcbb1bae5 100644 --- a/.ddev/homeadditions/.bashrc +++ b/.ddev/homeadditions/.bashrc @@ -14,7 +14,7 @@ shopt -s histappend export PS1="\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$\[$(tput sgr0)\] " -export PHP_IDE_CONFIG='serverName=etrapi.localhost' +export PHP_IDE_CONFIG='serverName=woocommerce-paypal-payments.ddev.site' #export XDEBUG_CONFIG="mode=debug client_host=127.0.0.1 client_port=9003 start_with_request=yes" alias ls='ls --group-directories-first' From b84177b61c71769672e480d5a590f15e39de95fe Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 6 May 2022 10:39:41 +0300 Subject: [PATCH 29/44] Simplify ddev .htaccess handling --- .../commands/web/orchestrate.d/10_wp_install.sh | 15 --------------- .ddev/commands/web/orchestrate.d/11_htaccess.sh | 16 ++++++++++++++++ .../web/orchestrate.d/60_flush_rewrites.sh | 7 +------ 3 files changed, 17 insertions(+), 21 deletions(-) create mode 100644 .ddev/commands/web/orchestrate.d/11_htaccess.sh diff --git a/.ddev/commands/web/orchestrate.d/10_wp_install.sh b/.ddev/commands/web/orchestrate.d/10_wp_install.sh index 417792bf3..b74209903 100644 --- a/.ddev/commands/web/orchestrate.d/10_wp_install.sh +++ b/.ddev/commands/web/orchestrate.d/10_wp_install.sh @@ -14,21 +14,6 @@ if [ "${WP_MULTISITE}" = "true" ]; then --admin_email="${ADMIN_EMAIL}" \ --skip-email - cat << 'EOF' >> ".htaccess" -RewriteEngine On -RewriteBase / -RewriteRule ^index\.php$ - [L] - -RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L] - -RewriteCond %{REQUEST_FILENAME} -f [OR] -RewriteCond %{REQUEST_FILENAME} -d -RewriteRule ^ - [L] -RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L] -RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L] -RewriteRule . index.php [L] -EOF - readarray -d , -t slugs <<< "${WP_MULTISITE_SLUGS},"; unset "slugs[-1]"; for slug in "${slugs[@]}"; do if [ ! -z "${slug}" ]; then diff --git a/.ddev/commands/web/orchestrate.d/11_htaccess.sh b/.ddev/commands/web/orchestrate.d/11_htaccess.sh new file mode 100644 index 000000000..40789ed22 --- /dev/null +++ b/.ddev/commands/web/orchestrate.d/11_htaccess.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +cat << 'EOF' >> ".htaccess" +RewriteEngine On +RewriteBase / +RewriteRule ^index\.php$ - [L] + +RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L] + +RewriteCond %{REQUEST_FILENAME} -f [OR] +RewriteCond %{REQUEST_FILENAME} -d +RewriteRule ^ - [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L] +RewriteRule . index.php [L] +EOF diff --git a/.ddev/commands/web/orchestrate.d/60_flush_rewrites.sh b/.ddev/commands/web/orchestrate.d/60_flush_rewrites.sh index 32fdc1064..d1bdc82bb 100644 --- a/.ddev/commands/web/orchestrate.d/60_flush_rewrites.sh +++ b/.ddev/commands/web/orchestrate.d/60_flush_rewrites.sh @@ -1,9 +1,4 @@ #!/bin/bash -# Needed for generating the .htaccess file -echo "apache_modules: - - mod_rewrite -" > wp-cli.yml - wp rewrite structure '/%postname%' -wp rewrite flush --hard +wp rewrite flush From a87a9dcab5f0cf2332f7e38099dd6f4058e2d3c7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 6 May 2022 11:34:58 +0300 Subject: [PATCH 30/44] Exclude .ddev from package --- .gitattributes | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitattributes b/.gitattributes index 533b2ddfc..23148506b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ /.github/ export-ignore /.psalm/ export-ignore /docker/ export-ignore +/.ddev/ export-ignore /tests/ export-ignore /.idea/ export-ignore .env export-ignore diff --git a/package.json b/package.json index 6670a82a4..09aaa2c29 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "prearchive": "rm -rf $npm_package_name.zip", "archive": "zip -r $npm_package_name.zip . -x **.git/\\* **node_modules/\\*", "postarchive": "yarn run archive:cleanup && rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name", - "archive:cleanup": "zip -d $npm_package_name.zip .env* docker/\\* docker-compose.yml .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store README.md .gitattributes .gitignore .travis.yml composer.json composer.lock package.json package-lock.json patchwork.json yarn.lock phpunit.xml.dist .phpunit.result.cache phpcs.xml.dist modules/ppcp-button/.babelrc modules/ppcp-button/package.json modules/ppcp-button/webpack.config.js modules/ppcp-button/yarn.lock vendor/\\*/.idea/\\* vendor/\\*/.gitignore vendor/\\*/.gitattributes vendor/\\*/.travis.yml" + "archive:cleanup": "zip -d $npm_package_name.zip .env* docker/\\* .ddev/\\* docker-compose.yml .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store README.md .gitattributes .gitignore .travis.yml composer.json composer.lock package.json package-lock.json patchwork.json yarn.lock phpunit.xml.dist .phpunit.result.cache phpcs.xml.dist modules/ppcp-button/.babelrc modules/ppcp-button/package.json modules/ppcp-button/webpack.config.js modules/ppcp-button/yarn.lock vendor/\\*/.idea/\\* vendor/\\*/.gitignore vendor/\\*/.gitattributes vendor/\\*/.travis.yml" }, "config": { "wp_org_slug": "woocommerce-paypal-payments" From 7e121c6a4ab5ad46fbb25c78e5e6094296799e94 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 6 May 2022 19:52:13 +0300 Subject: [PATCH 31/44] Update dev env docs --- README.md | 62 ++++++++++++++++++++++++++++------------------------ package.json | 16 ++++++++++++++ 2 files changed, 49 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index f1ba8a744..7d5812199 100644 --- a/README.md +++ b/README.md @@ -38,38 +38,41 @@ follow these steps: ``` $ yarn run build ``` -or if using the Docker setup: +or if using the DDEV setup: ``` -$ yarn run docker:build-package +$ yarn run ddev:build-package ``` ## Setup -You can install WooCommerce PayPal Payments locally using the dev environment of your preference, or you can use the Docker environment which includes WP, WC and all developments tools. +You can install WooCommerce PayPal Payments locally using the dev environment of your preference, or you can use the DDEV setup provided in this repository which includes WP, WC and all developments tools. -To set up the Docker environment, follow these steps: +To set up the DDEV environment, follow these steps: -0. Install Docker and Docker Compose. -1. `$ cp .env.example .env` and edit the configuration in the `.env` file if needed. -2. `$ yarn run docker:build` (or copy the commands from [package.json](/package.json) if you do not have `yarn`). -3. `$ yarn run docker:install` -4. `$ yarn run docker:start` -5. Add `127.0.0.1 wc-pp.myhost` to your `hosts` file and open http://wc-pp.myhost (the default value of `WP_DOMAIN` in `.env`). +0. Install Docker and [DDEV](https://ddev.readthedocs.io/en/stable/). +1. Edit the configuration in the [`.ddev/config.yml`](.ddev/config.yaml) file if needed. +2. `$ ddev start` +3. `$ ddev orchestrate` to install WP/WC. +4. Open https://wc-pp.ddev.site -### Running tests in the Docker environment +Use `$ ddev orchestrate -f` for reinstalattion (will destroy all site data). +You may also need `$ ddev restart` to apply the config changes. + +### Running tests and other tasks in the DDEV environment Tests and code style: -- `$ yarn run docker:test` -- `$ yarn run docker:lint` - -After some changes in `.env` (such as PHP, WP versions) you may need to rebuild the Docker image: - -1. `$ yarn run docker:destroy` (all data will be lost) -2. `$ yarn run docker:build` +- `$ yarn ddev:test` +- `$ yarn ddev:lint` +- `$ yarn ddev:fix-lint` - PHPCBF to fix basic code style issued See [package.json](/package.json) for other useful commands. +For debugging, see [the DDEV docs](https://ddev.readthedocs.io/en/stable/users/step-debugging/). +Enable xdebug via `$ ddev xdebug`, and press `Start Listening for PHP Debug Connections` in PHPStorm. +After creating the server in the PHPStorm dialog, you need to set the local project path for the server plugin path. +It should look [like this](https://i.imgur.com/ofsF1Mc.png). + ## Test account setup You will need a PayPal sandbox merchant and customer accounts to configure the plugin and make test purchases with it. @@ -82,20 +85,21 @@ For testing webhooks locally, follow these steps to set up ngrok: 0. Install [ngrok](https://ngrok.com/). -1. Run -``` -ngrok http -host-header=rewrite wc-pp.myhost -``` +1. + - If using DDEV, run our wrapper Bash script which will start `ddev share` and replace the URLs in the WP database: + ``` + $ .ddev/bin/share + ``` -2. In your environment variables (accessible to the web server), add `NGROK_HOST` with the host that you got from `ngrok`, like `abcd1234.ngrok.io`. - - - For the Docker environment: set `NGROK_HOST` in the `.env` file and restart the web server. (`yarn run docker:stop && yarn run docker:start`) - -3. Complete onboarding or resubscribe webhooks on the Webhooks Status page. - -Currently, ngrok is used only for the webhook listening URL. + - For other environments, run + ``` + $ ngrok http -host-header=rewrite wc-pp.myhost + ``` + and in your environment variables (accessible to the web server) add `NGROK_HOST` with the host that you got from `ngrok`, like `abcd1234.ngrok.io`. ngrok will be used only for the webhook listening URL. The URLs displayed on the WordPress pages, used in redirects, etc. will still remain local. +2. Complete onboarding or resubscribe webhooks on the Webhooks Status page. + ## License [GPL-2.0 License](LICENSE) diff --git a/package.json b/package.json index 09aaa2c29..d687a18f9 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,22 @@ "docker:fix-lint": "docker-compose run --rm test vendor/bin/phpcbf", "docker:build-package": "docker-compose run --rm build yarn run build", + "ddev:setup": "ddev start && ddev orchestrate", + "ddev:start": "ddev start", + "ddev:stop": "ddev stop", + "ddev:reset": "ddev orchestrate -f", + "ddev:install": "ddev composer install && yarn run ddev:build-js", + "ddev:build-js": "ddev yarn build:dev", + "ddev:composer-update": "ddev composer update && ddev composer update --lock", + "ddev:test": "ddev exec phpunit", + "ddev:lint": "yarn ddev:phpcs && yarn ddev:psalm", + "ddev:phpcs": "ddev exec phpcs --parallel=8 -s", + "ddev:psalm": "ddev exec psalm --show-info=false --threads=8 --diff", + "ddev:fix-lint": "ddev exec phpcbf", + "ddev:xdebug-on": "ddev xdebug", + "ddev:xdebug-off": "ddev xdebug", + "ddev:build-package": "ddev yarn build", + "prebuild": "rm -rf ./vendor && find . -name 'node_modules' -type d -maxdepth 3 -exec rm -rf {} +", "build": "composer install --no-dev && yarn run build:dev && yarn run archive", From 26f11a6e5e2010c9f5599baf14a5b2d0e7478cb2 Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 9 May 2022 11:29:31 +0300 Subject: [PATCH 32/44] Remove previous docker setup --- .env.example | 30 ------- .gitattributes | 3 - docker-compose.yml | 120 -------------------------- docker/Dockerfile_wp | 159 ----------------------------------- docker/wait-for-it.sh | 182 ---------------------------------------- docker/wp-entrypoint.sh | 45 ---------- package.json | 17 +--- 7 files changed, 1 insertion(+), 555 deletions(-) delete mode 100644 .env.example delete mode 100644 docker-compose.yml delete mode 100644 docker/Dockerfile_wp delete mode 100644 docker/wait-for-it.sh delete mode 100644 docker/wp-entrypoint.sh diff --git a/.env.example b/.env.example deleted file mode 100644 index 425800320..000000000 --- a/.env.example +++ /dev/null @@ -1,30 +0,0 @@ -PLUGIN_NAME=woocommerce-paypal-payments -BASE_PATH=./ -PROJECT_MOUNT_PATH=/var/www/html/wp-content/plugins/woocommerce-paypal-payments -DOCROOT_PATH=/var/www/html -BUILD_ROOT_PATH=/app/ -PROJECT_NAME=inpsyde_woocommerce-paypal-payments -HOST_IP_ADDRESS=172.17.0.1 - -WP_DOMAIN=wc-pp.myhost -WP_TITLE="WooCommerce PayPal Payments" - -WP_VERSION=5.3 -WC_VERSION=4.7.0 - -PHP_BUILD_VERSION=7.1 -PHP_TEST_VERSION=7.1 -PHP_DEPS_VERSION=7.1 - -DB_ROOT_PASSWORD=root -DB_NAME=wordpress -DB_USER_NAME=wordpress -DB_USER_PASSWORD=J(P*@%OSJiaifN2 - -ADMIN_USER=admin -ADMIN_PASS=admin -ADMIN_EMAIL=me@my.com - -#NGROK_HOST=mydomain.ngrok.io - -COMPOSER_MEMORY_LIMIT=1.5G diff --git a/.gitattributes b/.gitattributes index 23148506b..6774b417a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,5 @@ /.github/ export-ignore /.psalm/ export-ignore -/docker/ export-ignore /.ddev/ export-ignore /tests/ export-ignore /.idea/ export-ignore @@ -11,8 +10,6 @@ auth.json export-ignore composer.json export-ignore composer.lock export-ignore DEVELOPMENT.md export-ignore -docker-compose.yml export-ignore -grumphp.yml export-ignore gulpfile.js export-ignore package.json export-ignore phpcs.xml export-ignore diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 0f9d75055..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,120 +0,0 @@ -version: '3.4' -services: - wp_dev: - build: - context: ./ - dockerfile: docker/Dockerfile_wp - target: dev - args: - PHP_BUILD_VERSION: $PHP_BUILD_VERSION - PHP_TEST_VERSION: $PHP_TEST_VERSION - PHP_DEPS_VERSION: $PHP_DEPS_VERSION - PROJECT_MOUNT_PATH: $PROJECT_MOUNT_PATH - BUILD_ROOT_PATH: $BUILD_ROOT_PATH - DOCROOT_PATH: $DOCROOT_PATH - WP_DOMAIN: ${WP_DOMAIN} - WP_VERSION: $WP_VERSION - container_name: "${PROJECT_NAME}_wp_dev" - depends_on: - - db - restart: unless-stopped - environment: - WORDPRESS_DB_HOST: db:3306 - WORDPRESS_DB_NAME: $DB_NAME - WORDPRESS_DB_USER: $DB_USER_NAME - WORDPRESS_DB_PASSWORD: $DB_USER_PASSWORD - WORDPRESS_DEBUG: 1 - DOCROOT_PATH: ${DOCROOT_PATH} - PLUGIN_NAME: ${PLUGIN_NAME} - ADMIN_USER: ${ADMIN_USER} - ADMIN_PASS: ${ADMIN_PASS} - ADMIN_EMAIL: ${ADMIN_EMAIL} - WP_DOMAIN: ${WP_DOMAIN} - WP_TITLE: ${WP_TITLE} - WC_VERSION: $WC_VERSION - NGROK_HOST: ${NGROK_HOST} - - volumes: - - wordpress:${DOCROOT_PATH} - - ${BASE_PATH}:${PROJECT_MOUNT_PATH} - ports: - - 80:80 - - db: - image: mariadb:latest - container_name: "${PROJECT_NAME}_db" - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD - MYSQL_DATABASE: $DB_NAME - MYSQL_USER: $DB_USER_NAME - MYSQL_PASSWORD: $DB_USER_PASSWORD - ports: - - 3306:3306 - volumes: - - db:/var/lib/mysql - - db_admin: - image: phpmyadmin/phpmyadmin:latest - container_name: "${PROJECT_NAME}_db_admin" - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: $DB_ROOT_PASSWORD - depends_on: - - db - ports: - - 1234:80 - # volumes: - # - db_admin - - composer: - build: - context: ./ - dockerfile: docker/Dockerfile_wp - target: composer - args: - PHP_DEPS_VERSION: $PHP_DEPS_VERSION - BUILD_ROOT_PATH: $BUILD_ROOT_PATH - container_name: "${PROJECT_NAME}_composer" - working_dir: ${BUILD_ROOT_PATH} - volumes: - - ${BASE_PATH}:${BUILD_ROOT_PATH} - environment: - - COMPOSER_MEMORY_LIMIT=${COMPOSER_MEMORY_LIMIT} - - build: - build: - context: ./ - dockerfile: docker/Dockerfile_wp - target: build - args: - PHP_DEPS_VERSION: $PHP_DEPS_VERSION - PHP_BUILD_VERSION: $PHP_BUILD_VERSION - BUILD_ROOT_PATH: $BUILD_ROOT_PATH - WP_VERSION: $WP_VERSION - container_name: "${PROJECT_NAME}_build" - working_dir: ${BUILD_ROOT_PATH} - volumes: - - ${BASE_PATH}:${BUILD_ROOT_PATH} - - test: - extra_hosts: - - "host.docker.internal:${HOST_IP_ADDRESS}" - build: - context: ./ - dockerfile: docker/Dockerfile_wp - target: test - args: - PHP_DEPS_VERSION: $PHP_DEPS_VERSION - BUILD_ROOT_PATH: $BUILD_ROOT_PATH - PHP_BUILD_VERSION: $PHP_BUILD_VERSION - PHP_TEST_VERSION: $PHP_TEST_VERSION - container_name: "${PROJECT_NAME}_test" - working_dir: ${BUILD_ROOT_PATH} - volumes: - - ${BASE_PATH}:${BUILD_ROOT_PATH} - -volumes: - wordpress: - db: - db_admin: diff --git a/docker/Dockerfile_wp b/docker/Dockerfile_wp deleted file mode 100644 index a698095d7..000000000 --- a/docker/Dockerfile_wp +++ /dev/null @@ -1,159 +0,0 @@ -ARG PHP_BUILD_VERSION -ARG PHP_TEST_VERSION -ARG PHP_DEPS_VERSION -ARG WP_VERSION - -# Composer on correct PHP version -FROM php:${PHP_DEPS_VERSION}-cli as composer - -ARG BUILD_ROOT_PATH - -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -RUN echo 'memory_limit = 128M' >> /usr/local/etc/php/conf.d/docker-php-memlimit.ini; - -RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \ - php composer-setup.php --install-dir=/usr/bin --filename=composer --version=1.10.22 && \ - php -r "unlink('composer-setup.php');" - -RUN apt-get update -RUN apt-get install -y \ - ssh \ - zip \ - unzip \ - curl \ - git \ - # These are for extensions - zlib1g-dev \ - libicu-dev - -RUN mkdir -p ~/.ssh -RUN ssh-keyscan -H github.com >> ~/.ssh/known_hosts - -RUN docker-php-ext-install intl json && \ - docker-php-ext-enable intl json - -WORKDIR ${BUILD_ROOT_PATH} - -# Composer on correct PHP version -FROM php:${PHP_BUILD_VERSION}-cli as build - -ARG BUILD_ROOT_PATH - -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -RUN echo 'memory_limit = 256M' >> /usr/local/etc/php/conf.d/docker-php-memlimit.ini; - -RUN apt-get update -RUN apt-get install -y gnupg apt-transport-https ca-certificates - -RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ - echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list - -RUN apt-get update -RUN apt-get install -y \ - ssh \ - zip \ - unzip \ - curl \ - git \ - yarn \ - ca-certificates \ - # These are for extensions - zlib1g-dev \ - libicu-dev \ - g++ \ - # For installing things from URL - wget - -# https://github.com/nodesource/distributions/issues/1266 -RUN update-ca-certificates - -RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && \ - php composer-setup.php --install-dir=/usr/bin --filename=composer --version=1.10.22 && \ - php -r "unlink('composer-setup.php');" - -RUN mkdir -p ~/.ssh -RUN ssh-keyscan -H github.com >> ~/.ssh/known_hosts - -RUN docker-php-ext-install intl json && \ - docker-php-ext-enable intl json - -# Install Node -RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - -RUN apt-get install -y nodejs - -WORKDIR ${BUILD_ROOT_PATH} -COPY . ./ - - -FROM php:${PHP_TEST_VERSION}-cli as test - -ARG BUILD_ROOT_PATH - -RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" -RUN apt-get update -RUN apt-get install -y \ - # These are for extensions - zlib1g-dev \ - libicu-dev \ - g++ -RUN pecl install xdebug-2.6.1 -RUN docker-php-ext-install pcntl posix intl json - -WORKDIR ${BUILD_ROOT_PATH} -COPY --from=build ${BUILD_ROOT_PATH} ${BUILD_ROOT_PATH} - - -# Install PHP dev dependencies -FROM build as vendor-dev - -ARG BUILD_ROOT_PATH - -WORKDIR ${BUILD_ROOT_PATH} -COPY --from=build ${BUILD_ROOT_PATH} ${BUILD_ROOT_PATH} - -RUN composer config discard-changes true && composer install --no-dev --no-scripts - - -# WordPress for development -FROM wordpress:${WP_VERSION}-php${PHP_TEST_VERSION}-apache as dev - -ARG PROJECT_MOUNT_PATH -ARG BUILD_ROOT_PATH -ARG DOCROOT_PATH -ARG WP_DOMAIN - -COPY docker/wp-entrypoint.sh /usr/local/bin -COPY docker/wait-for-it.sh /usr/local/bin - -RUN chmod +x /usr/local/bin/wp-entrypoint.sh /usr/local/bin/wait-for-it.sh - -RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \ - && chmod +x wp-cli.phar \ - && mv wp-cli.phar /usr/local/bin/wp -RUN sed -i "s|#ServerName www.example.com|ServerName ${WP_DOMAIN}|" /etc/apache2/sites-available/*.conf -RUN sed -i "s|#ServerName www.example.com|ServerName ${WP_DOMAIN}|" /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf - - -RUN apt-get update -RUN apt-get install -y \ - zip \ - unzip \ - curl \ - # These are for extensions - zlib1g-dev \ - libicu-dev \ - g++ - -RUN docker-php-ext-install pcntl posix intl json - -RUN apt-get remove -y \ - # These are for extensions - zlib1g-dev \ - libicu-dev \ - g++ - -WORKDIR ${DOCROOT_PATH} -COPY --from=vendor-dev ${BUILD_ROOT_PATH} ${PROJECT_MOUNT_PATH} - -ENTRYPOINT ["wp-entrypoint.sh"] -CMD ["apache2-foreground"] diff --git a/docker/wait-for-it.sh b/docker/wait-for-it.sh deleted file mode 100644 index 5e8679e54..000000000 --- a/docker/wait-for-it.sh +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/env bash -# Use this script to test if a given TCP host/port are available - -WAITFORIT_cmdname=${0##*/} - -echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } - -usage() -{ - cat << USAGE >&2 -Usage: - $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] - -h HOST | --host=HOST Host or IP under test - -p PORT | --port=PORT TCP port under test - Alternatively, you specify the host and port as host:port - -s | --strict Only execute subcommand if the test succeeds - -q | --quiet Don't output any status messages - -t TIMEOUT | --timeout=TIMEOUT - Timeout in seconds, zero for no timeout - -- COMMAND ARGS Execute command with args after the test finishes -USAGE - exit 1 -} - -wait_for() -{ - if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then - echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" - else - echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" - fi - WAITFORIT_start_ts=$(date +%s) - while : - do - if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then - nc -z $WAITFORIT_HOST $WAITFORIT_PORT - WAITFORIT_result=$? - else - (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 - WAITFORIT_result=$? - fi - if [[ $WAITFORIT_result -eq 0 ]]; then - WAITFORIT_end_ts=$(date +%s) - echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" - break - fi - sleep 1 - done - return $WAITFORIT_result -} - -wait_for_wrapper() -{ - # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 - if [[ $WAITFORIT_QUIET -eq 1 ]]; then - timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & - else - timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & - fi - WAITFORIT_PID=$! - trap "kill -INT -$WAITFORIT_PID" INT - wait $WAITFORIT_PID - WAITFORIT_RESULT=$? - if [[ $WAITFORIT_RESULT -ne 0 ]]; then - echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" - fi - return $WAITFORIT_RESULT -} - -# process arguments -while [[ $# -gt 0 ]] -do - case "$1" in - *:* ) - WAITFORIT_hostport=(${1//:/ }) - WAITFORIT_HOST=${WAITFORIT_hostport[0]} - WAITFORIT_PORT=${WAITFORIT_hostport[1]} - shift 1 - ;; - --child) - WAITFORIT_CHILD=1 - shift 1 - ;; - -q | --quiet) - WAITFORIT_QUIET=1 - shift 1 - ;; - -s | --strict) - WAITFORIT_STRICT=1 - shift 1 - ;; - -h) - WAITFORIT_HOST="$2" - if [[ $WAITFORIT_HOST == "" ]]; then break; fi - shift 2 - ;; - --host=*) - WAITFORIT_HOST="${1#*=}" - shift 1 - ;; - -p) - WAITFORIT_PORT="$2" - if [[ $WAITFORIT_PORT == "" ]]; then break; fi - shift 2 - ;; - --port=*) - WAITFORIT_PORT="${1#*=}" - shift 1 - ;; - -t) - WAITFORIT_TIMEOUT="$2" - if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi - shift 2 - ;; - --timeout=*) - WAITFORIT_TIMEOUT="${1#*=}" - shift 1 - ;; - --) - shift - WAITFORIT_CLI=("$@") - break - ;; - --help) - usage - ;; - *) - echoerr "Unknown argument: $1" - usage - ;; - esac -done - -if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then - echoerr "Error: you need to provide a host and port to test." - usage -fi - -WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} -WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} -WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} -WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} - -# Check to see if timeout is from busybox? -WAITFORIT_TIMEOUT_PATH=$(type -p timeout) -WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) - -WAITFORIT_BUSYTIMEFLAG="" -if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then - WAITFORIT_ISBUSY=1 - # Check if busybox timeout uses -t flag - # (recent Alpine versions don't support -t anymore) - if timeout &>/dev/stdout | grep -q -e '-t '; then - WAITFORIT_BUSYTIMEFLAG="-t" - fi -else - WAITFORIT_ISBUSY=0 -fi - -if [[ $WAITFORIT_CHILD -gt 0 ]]; then - wait_for - WAITFORIT_RESULT=$? - exit $WAITFORIT_RESULT -else - if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then - wait_for_wrapper - WAITFORIT_RESULT=$? - else - wait_for - WAITFORIT_RESULT=$? - fi -fi - -if [[ $WAITFORIT_CLI != "" ]]; then - if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then - echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" - exit $WAITFORIT_RESULT - fi - exec "${WAITFORIT_CLI[@]}" -else - exit $WAITFORIT_RESULT -fi diff --git a/docker/wp-entrypoint.sh b/docker/wp-entrypoint.sh deleted file mode 100644 index 0b522cf07..000000000 --- a/docker/wp-entrypoint.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -set -e - -if wait-for-it.sh "${WORDPRESS_DB_HOST}" -t 60; then - docker-entrypoint.sh apache2 -v - - wp core multisite-install \ - --allow-root \ - --title="${WP_TITLE}" \ - --admin_user="${ADMIN_USER}" \ - --admin_password="${ADMIN_PASS}" \ - --url="${WP_DOMAIN}" \ - --admin_email="${ADMIN_EMAIL}" \ - --skip-email - - cat << 'EOF' > "${DOCROOT_PATH}/.htaccess" -RewriteEngine On -RewriteBase / -RewriteRule ^index\.php$ - [L] - -RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L] - -RewriteCond %{REQUEST_FILENAME} -f [OR] -RewriteCond %{REQUEST_FILENAME} -d -RewriteRule ^ - [L] -RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L] -RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L] -RewriteRule . index.php [L] -EOF - - wp site create \ - --allow-root \ - --slug="de" \ - || true # allow failure if already exists - - wp plugin is-installed akismet --allow-root && wp plugin uninstall akismet --allow-root --path="${DOCROOT_PATH}" - wp plugin is-installed hello --allow-root && wp plugin uninstall hello --allow-root --path="${DOCROOT_PATH}" - - wp plugin install woocommerce --version="${WC_VERSION}" --allow-root --path="${DOCROOT_PATH}" \ - && wp plugin activate woocommerce --network --allow-root --path="${DOCROOT_PATH}" - - wp plugin activate "${PLUGIN_NAME}" --network --allow-root --path="${DOCROOT_PATH}" -fi - -exec "$@" diff --git a/package.json b/package.json index d687a18f9..435121535 100644 --- a/package.json +++ b/package.json @@ -18,20 +18,6 @@ "build:modules": "yarn run build:modules:ppcp-button && yarn build:modules:ppcp-wc-gateway && yarn build:modules:ppcp-webhooks && yarn build:modules:ppcp-vaulting", "build:dev": "yarn run install:modules && yarn run build:modules", - "docker:build": "docker-compose build", - "docker:start": "docker-compose up -d wp_dev", - "docker:stop": "docker-compose down", - "docker:destroy": "docker-compose down -v", - "docker:logs": "docker-compose logs", - "docker:shell": "docker-compose run --rm wp_dev bash", - "docker:install": "docker-compose run --rm composer composer install && yarn run docker:build-js", - "docker:build-js": "docker-compose run --rm build yarn run build:dev", - "docker:composer-update": "docker-compose run --rm composer composer update && docker-compose run --rm composer composer update --lock", - "docker:test": "docker-compose run --rm test vendor/bin/phpunit", - "docker:lint": "docker-compose run --rm test sh -c 'vendor/bin/phpcs --parallel=8 -s && vendor/bin/psalm --show-info=false --threads=8 --diff'", - "docker:fix-lint": "docker-compose run --rm test vendor/bin/phpcbf", - "docker:build-package": "docker-compose run --rm build yarn run build", - "ddev:setup": "ddev start && ddev orchestrate", "ddev:start": "ddev start", "ddev:stop": "ddev stop", @@ -48,13 +34,12 @@ "ddev:xdebug-off": "ddev xdebug", "ddev:build-package": "ddev yarn build", - "prebuild": "rm -rf ./vendor && find . -name 'node_modules' -type d -maxdepth 3 -exec rm -rf {} +", "build": "composer install --no-dev && yarn run build:dev && yarn run archive", "prearchive": "rm -rf $npm_package_name.zip", "archive": "zip -r $npm_package_name.zip . -x **.git/\\* **node_modules/\\*", "postarchive": "yarn run archive:cleanup && rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name", - "archive:cleanup": "zip -d $npm_package_name.zip .env* docker/\\* .ddev/\\* docker-compose.yml .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store README.md .gitattributes .gitignore .travis.yml composer.json composer.lock package.json package-lock.json patchwork.json yarn.lock phpunit.xml.dist .phpunit.result.cache phpcs.xml.dist modules/ppcp-button/.babelrc modules/ppcp-button/package.json modules/ppcp-button/webpack.config.js modules/ppcp-button/yarn.lock vendor/\\*/.idea/\\* vendor/\\*/.gitignore vendor/\\*/.gitattributes vendor/\\*/.travis.yml" + "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store README.md .gitattributes .gitignore .travis.yml composer.json composer.lock package.json package-lock.json patchwork.json yarn.lock phpunit.xml.dist .phpunit.result.cache phpcs.xml.dist modules/ppcp-button/.babelrc modules/ppcp-button/package.json modules/ppcp-button/webpack.config.js modules/ppcp-button/yarn.lock vendor/\\*/.idea/\\* vendor/\\*/.gitignore vendor/\\*/.gitattributes vendor/\\*/.travis.yml" }, "config": { "wp_org_slug": "woocommerce-paypal-payments" From 0052aacbbccd0fe95f14c09fa70f11051b88d063 Mon Sep 17 00:00:00 2001 From: helgatheviking <507025+helgatheviking@users.noreply.github.com> Date: Mon, 9 May 2022 13:19:32 -0600 Subject: [PATCH 33/44] Rename new filter to woocommerce_paypal_payments_product_supports_payment_request_button to avoid abbreviates --- modules/ppcp-button/src/Assets/SmartButton.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 8ee890e90..b13667902 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -423,7 +423,7 @@ class SmartButton implements SmartButtonInterface { if ( ! is_checkout() && is_a( $product, \WC_Product::class ) - && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_prb', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) + && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_payment_request_button', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) ) ) { return; @@ -440,7 +440,7 @@ class SmartButton implements SmartButtonInterface { if ( ! is_checkout() && is_a( $product, \WC_Product::class ) - && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_prb', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) + && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_payment_request_button', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) ) ) { return; From ab0e90dab0d8eadb2b94678b4951e7acb57865ea Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 10 May 2022 18:46:40 +0400 Subject: [PATCH 34/44] Store order info in wc order meta to get if there is no session. --- .../js/modules/Renderer/CreditCardRenderer.js | 6 ------ .../src/Endpoint/CreateOrderEndpoint.php | 7 +++++++ .../src/Processor/OrderProcessor.php | 3 ++- .../WcGateway/Processor/OrderProcessorTest.php | 17 +++++++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js index b06edf9bb..d87f82022 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js @@ -207,12 +207,6 @@ class CreditCardRenderer { const firstName = document.getElementById('billing_first_name') ? document.getElementById('billing_first_name').value : ''; const lastName = document.getElementById('billing_last_name') ? document.getElementById('billing_last_name').value : ''; - if (!firstName || !lastName) { - this.spinner.unblock(); - this.errorHandler.message(this.defaultConfig.hosted_fields.labels.cardholder_name_required); - return; - } - hostedFieldsData.cardholderName = firstName + ' ' + lastName; } diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 89c5faa54..5dd2a14dc 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -242,6 +242,13 @@ class CreateOrderEndpoint implements EndpointInterface { } $order = $this->create_paypal_order( $wc_order ); + + if ( 'pay-now' === $data['context'] && is_a( $wc_order, \WC_Order::class ) ) { + $wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() ); + $wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() ); + $wc_order->save_meta_data(); + } + wp_send_json_success( $order->to_array() ); return true; diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php index 170cb50e8..b1d3358bd 100644 --- a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php +++ b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php @@ -150,7 +150,8 @@ class OrderProcessor { * @return bool */ public function process( \WC_Order $wc_order ): bool { - $order = $this->session_handler->order(); + $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ); + $order = $this->session_handler->order() ?? $this->order_endpoint->order( $order_id ); if ( ! $order ) { $this->last_error = __( 'No PayPal order found in the current WooCommerce session.', 'woocommerce-paypal-payments' ); return false; diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php index e379a4fc1..ae051a68c 100644 --- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php +++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php @@ -89,6 +89,11 @@ class OrderProcessorTest extends TestCase ->shouldReceive('payment_source') ->andReturn(null); + $wcOrder + ->shouldReceive('get_meta') + ->with(PayPalGateway::ORDER_ID_META_KEY) + ->andReturn(1); + $sessionHandler = Mockery::mock(SessionHandler::class); $sessionHandler ->expects('order') @@ -208,6 +213,12 @@ class OrderProcessorTest extends TestCase $currentOrder ->shouldReceive('payment_source') ->andReturn(null); + + $wcOrder + ->shouldReceive('get_meta') + ->with(PayPalGateway::ORDER_ID_META_KEY) + ->andReturn(1); + $sessionHandler = Mockery::mock(SessionHandler::class); $sessionHandler ->expects('order') @@ -313,6 +324,12 @@ class OrderProcessorTest extends TestCase $currentOrder ->shouldReceive('purchase_units') ->andReturn([$purchaseUnit]); + + $wcOrder + ->shouldReceive('get_meta') + ->with(PayPalGateway::ORDER_ID_META_KEY) + ->andReturn(1); + $sessionHandler = Mockery::mock(SessionHandler::class); $sessionHandler ->expects('order') From 34fdae6bd579fc43a5950471e3602b0cc227e2cd Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 11 May 2022 11:37:54 +0300 Subject: [PATCH 35/44] Create wordpress directory during ddev start Otherwise it will be created automatically with wrong permissions --- .ddev/config.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.ddev/config.yaml b/.ddev/config.yaml index c0e154f9a..3d732fd50 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -14,6 +14,9 @@ nfs_mount_enabled: false mutagen_enabled: false use_dns_when_possible: true composer_version: "1" +hooks: + pre-start: + - exec-host: "mkdir -p .ddev/wordpress/wp-content/plugins/${DDEV_PROJECT}" web_environment: - WP_VERSION=5.9.3 - WP_LOCALE=en_US From fc3ba70d4be7f0dbede1f1eddcdec8ba16b81547 Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 11 May 2022 18:16:50 +0300 Subject: [PATCH 36/44] Extract filter to a function --- .../ppcp-button/src/Assets/SmartButton.php | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index cce84e9ac..61a5c21ed 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Button\Assets; use Exception; use Psr\Log\LoggerInterface; +use WC_Product; use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; @@ -524,9 +525,8 @@ class SmartButton implements SmartButtonInterface { $product = wc_get_product(); if ( - ! is_checkout() && is_a( $product, \WC_Product::class ) - && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_payment_request_button', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) - ) + ! is_checkout() && is_a( $product, WC_Product::class ) + && ! $this->product_supports_payment( $product ) ) { return; @@ -550,9 +550,11 @@ class SmartButton implements SmartButtonInterface { $product = wc_get_product(); if ( - ! is_checkout() && is_a( $product, \WC_Product::class ) - && ( ! apply_filters( 'woocommerce_paypal_payments_product_supports_payment_request_button', ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), $product ) - ) + ! is_checkout() && is_a( $product, WC_Product::class ) + /** + * The filter returning true if PayPal buttons can be rendered, or false otherwise. + */ + && ! $this->product_supports_payment( $product ) ) { return; } @@ -576,7 +578,7 @@ class SmartButton implements SmartButtonInterface { } $placement = 'product'; $product = wc_get_product(); - $amount = ( is_a( $product, \WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0; + $amount = ( is_a( $product, WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0; $layout = $this->settings->has( 'message_product_layout' ) ? $this->settings->get( 'message_product_layout' ) : 'text'; $logo_type = $this->settings->has( 'message_product_logo' ) ? @@ -1235,6 +1237,24 @@ class SmartButton implements SmartButtonInterface { return WC()->cart->get_cart_contents_total() == 0; } + /** + * Checks if PayPal buttons/messages can be rendered for the given product. + * + * @param WC_Product $product The product. + * + * @return bool + */ + protected function product_supports_payment( WC_Product $product ): bool { + /** + * The filter returning true if PayPal buttons/messages can be rendered for this product, or false otherwise. + */ + return apply_filters( + 'woocommerce_paypal_payments_product_supports_payment_request_button', + ! $product->is_type( array( 'external', 'grouped' ) ) && $product->is_in_stock(), + $product + ); + } + /** * Retrieves all payment tokens for the user, via API or cached if already queried. * From f7d0f60ca70916f972946b8ae7475548372797cd Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 11 May 2022 18:17:24 +0300 Subject: [PATCH 37/44] Ignore wrong psalm error https://github.com/php-stubs/wordpress-stubs/issues/2 --- psalm.xml.dist | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/psalm.xml.dist b/psalm.xml.dist index f9fe7e1d0..175dc4114 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -151,5 +151,11 @@ + + + + + + From 65007a7baef71cc4e633d0e4239c934e7d50c2e7 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 12 May 2022 17:37:56 +0300 Subject: [PATCH 38/44] Do not handle vaulting settings change when not onboarded Otherwise vaulting gets disabled after disconnect and re-onboarding --- modules/ppcp-wc-gateway/src/Settings/SettingsListener.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index 6ddf945d4..76ffbe43d 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -167,7 +167,7 @@ class SettingsListener { * Prevent enabling both Pay Later messaging and PayPal vaulting */ public function listen_for_vaulting_enabled() { - if ( ! $this->is_valid_site_request() ) { + if ( ! $this->is_valid_site_request() || State::STATE_ONBOARDED !== $this->state->current_state() ) { return; } From d4dcc10ef6ad80237563533de5fd05de8e16745f Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 12 May 2022 17:38:22 +0300 Subject: [PATCH 39/44] Remove outdated comment --- .../ppcp-wc-gateway/src/Settings/SettingsListener.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index 76ffbe43d..b43f89d0c 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -441,19 +441,10 @@ class SettingsListener { */ private function is_valid_site_request() : bool { - /** - * No nonce needed at this point. - * - * phpcs:disable WordPress.Security.NonceVerification.Missing - * phpcs:disable WordPress.Security.NonceVerification.Recommended - */ if ( empty( $this->page_id ) ) { return false; } - // phpcs:enable WordPress.Security.NonceVerification.Missing - // phpcs:enable WordPress.Security.NonceVerification.Recommended - if ( ! current_user_can( 'manage_woocommerce' ) ) { return false; } From c933c595d8f4ca9a9060cf2f255037bbb3b75e17 Mon Sep 17 00:00:00 2001 From: Alex P Date: Thu, 12 May 2022 17:39:30 +0300 Subject: [PATCH 40/44] Remove redirect after vaulting settings check does not seem to be needed --- modules/ppcp-wc-gateway/src/Settings/SettingsListener.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index b43f89d0c..6552008e9 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -208,10 +208,6 @@ class SettingsListener { $this->settings->set( 'message_product_enabled', false ); $this->settings->set( 'message_cart_enabled', false ); $this->settings->persist(); - - $redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ); - wp_safe_redirect( $redirect_url, 302 ); - exit; } /** From a8c1f386aad2938be7afa1c9d626e6ae186201c2 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 13 May 2022 17:09:58 +0300 Subject: [PATCH 41/44] Fix item description with emoji --- modules/ppcp-api-client/src/Factory/ItemFactory.php | 2 +- tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/src/Factory/ItemFactory.php b/modules/ppcp-api-client/src/Factory/ItemFactory.php index fe1335476..ea639e704 100644 --- a/modules/ppcp-api-client/src/Factory/ItemFactory.php +++ b/modules/ppcp-api-client/src/Factory/ItemFactory.php @@ -142,7 +142,7 @@ class ItemFactory { mb_substr( $product->get_name(), 0, 127 ), new Money( $price_without_tax_rounded, $currency ), $quantity, - mb_substr( wp_strip_all_tags( $product->get_description() ), 0, 127 ), + substr( wp_strip_all_tags( $product->get_description() ), 0, 127 ) ?: '', $tax, $product->get_sku(), ( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS diff --git a/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php index ee89d6f5c..0decea0f8 100644 --- a/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php +++ b/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php @@ -300,7 +300,7 @@ class ItemFactoryTest extends TestCase * @var Item $item */ $this->assertEquals(mb_substr($name, 0, 127), $item->name()); - $this->assertEquals(mb_substr($description, 0, 127), $item->description()); + $this->assertEquals(substr($description, 0, 127), $item->description()); } public function testFromPayPalResponse() From d63f89a4a2f294a8dff7b6ae31141a6b032f650b Mon Sep 17 00:00:00 2001 From: Alex P Date: Wed, 4 May 2022 16:23:54 +0300 Subject: [PATCH 42/44] Retry without shipping field when got invalid address error --- .../src/Entity/PurchaseUnit.php | 9 ++++ .../src/Exception/PayPalApiException.php | 9 ++++ .../src/Endpoint/CreateOrderEndpoint.php | 49 ++++++++++++++++--- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-api-client/src/Entity/PurchaseUnit.php b/modules/ppcp-api-client/src/Entity/PurchaseUnit.php index 0eaa1076d..f9fa0228e 100644 --- a/modules/ppcp-api-client/src/Entity/PurchaseUnit.php +++ b/modules/ppcp-api-client/src/Entity/PurchaseUnit.php @@ -175,6 +175,15 @@ class PurchaseUnit { return $this->shipping; } + /** + * Sets shipping info. + * + * @param Shipping|null $shipping The value to set. + */ + public function set_shipping( ?Shipping $shipping ): void { + $this->shipping = $shipping; + } + /** * Returns the reference id. * diff --git a/modules/ppcp-api-client/src/Exception/PayPalApiException.php b/modules/ppcp-api-client/src/Exception/PayPalApiException.php index 699bb2423..d5720532b 100644 --- a/modules/ppcp-api-client/src/Exception/PayPalApiException.php +++ b/modules/ppcp-api-client/src/Exception/PayPalApiException.php @@ -119,4 +119,13 @@ class PayPalApiException extends RuntimeException { public function issues(): array { return $this->response->issues ?? array(); } + + /** + * The HTTP status code. + * + * @return int + */ + public function status_code(): int { + return $this->status_code; + } } diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php index 89c5faa54..f2e7b7be1 100644 --- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php +++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint; use Exception; use Psr\Log\LoggerInterface; +use stdClass; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount; use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; @@ -317,19 +318,51 @@ class CreateOrderEndpoint implements EndpointInterface { * @return Order Created PayPal order. * * @throws RuntimeException If create order request fails. + * @throws PayPalApiException If create order request fails. + * phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.WrongNumber */ private function create_paypal_order( \WC_Order $wc_order = null ): Order { $needs_shipping = WC()->cart instanceof \WC_Cart && WC()->cart->needs_shipping(); $shipping_address_is_fix = $needs_shipping && 'checkout' === $this->parsed_request_data['context']; - return $this->api_endpoint->create( - $this->purchase_units, - $this->payer( $this->parsed_request_data, $wc_order ), - null, - $this->payment_method(), - '', - $shipping_address_is_fix - ); + try { + return $this->api_endpoint->create( + $this->purchase_units, + $this->payer( $this->parsed_request_data, $wc_order ), + null, + $this->payment_method(), + '', + $shipping_address_is_fix + ); + } catch ( PayPalApiException $exception ) { + // Looks like currently there is no proper way to validate the shipping address for PayPal, + // so we cannot make some invalid addresses null in PurchaseUnitFactory, + // which causes failure e.g. for guests using the button on products pages when the country does not have postal codes. + if ( 422 === $exception->status_code() + && array_filter( + $exception->details(), + function ( stdClass $detail ): bool { + return isset( $detail->field ) && str_contains( (string) $detail->field, 'shipping/address' ); + } + ) ) { + $this->logger->info( 'Invalid shipping address for order creation, retrying without it.' ); + + foreach ( $this->purchase_units as $purchase_unit ) { + $purchase_unit->set_shipping( null ); + } + + return $this->api_endpoint->create( + $this->purchase_units, + $this->payer( $this->parsed_request_data, $wc_order ), + null, + $this->payment_method(), + '', + $shipping_address_is_fix + ); + } + + throw $exception; + } } /** From 88220aa3c6e7ed044686be7b16176f1ceca98c73 Mon Sep 17 00:00:00 2001 From: Alex P Date: Tue, 10 May 2022 18:09:48 +0300 Subject: [PATCH 43/44] Do not send negative price items, add to discount --- .../src/Factory/AmountFactory.php | 54 +++++++-- .../src/Factory/PurchaseUnitFactory.php | 14 ++- .../Factory/PurchaseUnitFactoryTest.php | 108 ++++++++++++++---- 3 files changed, 141 insertions(+), 35 deletions(-) diff --git a/modules/ppcp-api-client/src/Factory/AmountFactory.php b/modules/ppcp-api-client/src/Factory/AmountFactory.php index bf9637c32..e379fc844 100644 --- a/modules/ppcp-api-client/src/Factory/AmountFactory.php +++ b/modules/ppcp-api-client/src/Factory/AmountFactory.php @@ -124,6 +124,27 @@ class AmountFactory { $currency = $order->get_currency(); $items = $this->item_factory->from_wc_order( $order ); + $discount_value = array_sum( + array( + (float) $order->get_total_discount( false ), // Only coupons. + $this->discounts_from_items( $items ), + ) + ); + $discount = null; + if ( $discount_value ) { + $discount = new Money( + (float) $discount_value, + $currency + ); + } + + $items = array_filter( + $items, + function ( Item $item ): bool { + return $item->unit_amount()->value() > 0; + } + ); + $total_value = (float) $order->get_total(); if ( ( CreditCardGateway::ID === $order->get_payment_method() @@ -160,14 +181,6 @@ class AmountFactory { $currency ); - $discount = null; - if ( (float) $order->get_total_discount( false ) ) { - $discount = new Money( - (float) $order->get_total_discount( false ), - $currency - ); - } - $breakdown = new AmountBreakdown( $item_total, $shipping, @@ -251,4 +264,29 @@ class AmountFactory { return new AmountBreakdown( ...$money ); } + + /** + * Returns the sum of items with negative amount; + * + * @param Item[] $items PayPal order items. + * @return float + */ + private function discounts_from_items( array $items ): float { + $discounts = array_filter( + $items, + function ( Item $item ): bool { + return $item->unit_amount()->value() < 0; + } + ); + return abs( + array_sum( + array_map( + function ( Item $item ): float { + return (float) $item->quantity() * $item->unit_amount()->value(); + }, + $discounts + ) + ) + ); + } } diff --git a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php index 37c371fb0..303b7bc6a 100644 --- a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php +++ b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php @@ -107,7 +107,12 @@ class PurchaseUnitFactory { */ public function from_wc_order( \WC_Order $order ): PurchaseUnit { $amount = $this->amount_factory->from_wc_order( $order ); - $items = $this->item_factory->from_wc_order( $order ); + $items = array_filter( + $this->item_factory->from_wc_order( $order ), + function ( Item $item ): bool { + return $item->unit_amount()->value() > 0; + } + ); $shipping = $this->shipping_factory->from_wc_order( $order ); if ( ! $this->shipping_needed( ... array_values( $items ) ) || @@ -153,7 +158,12 @@ class PurchaseUnitFactory { */ public function from_wc_cart( \WC_Cart $cart ): PurchaseUnit { $amount = $this->amount_factory->from_wc_cart( $cart ); - $items = $this->item_factory->from_wc_cart( $cart ); + $items = array_filter( + $this->item_factory->from_wc_cart( $cart ), + function ( Item $item ): bool { + return $item->unit_amount()->value() > 0; + } + ); $shipping = null; $customer = \WC()->customer; diff --git a/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php index 0450c1a90..72c664c91 100644 --- a/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php +++ b/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php @@ -6,6 +6,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory; use WooCommerce\PayPalCommerce\ApiClient\Entity\Address; use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount; use WooCommerce\PayPalCommerce\ApiClient\Entity\Item; +use WooCommerce\PayPalCommerce\ApiClient\Entity\Money; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payee; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments; use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit; @@ -21,7 +22,19 @@ class PurchaseUnitFactoryTest extends TestCase private $wcOrderId = 1; private $wcOrderNumber = '100000'; - public function testWcOrderDefault() + private $item; + + public function setUp(): void + { + parent::setUp(); + + $this->item = Mockery::mock(Item::class, [ + 'category' => Item::PHYSICAL_GOODS, + 'unit_amount' => new Money(42.5, 'USD'), + ]); + } + + public function testWcOrderDefault() { $wcOrder = Mockery::mock(\WC_Order::class); $wcOrder->expects('get_order_number')->andReturn($this->wcOrderNumber); @@ -37,13 +50,11 @@ class PurchaseUnitFactoryTest extends TestCase $payee = Mockery::mock(Payee::class); $payeeRepository ->shouldReceive('payee')->andReturn($payee); - $item = Mockery::mock(Item::class); - $item->shouldReceive('category')->andReturn(Item::PHYSICAL_GOODS); $itemFactory = Mockery::mock(ItemFactory::class); $itemFactory ->shouldReceive('from_wc_order') ->with($wcOrder) - ->andReturn([$item]); + ->andReturn([$this->item]); $address = Mockery::mock(Address::class); $address @@ -79,11 +90,67 @@ class PurchaseUnitFactoryTest extends TestCase $this->assertEquals($this->wcOrderId, $unit->custom_id()); $this->assertEquals('', $unit->soft_descriptor()); $this->assertEquals('WC-' . $this->wcOrderNumber, $unit->invoice_id()); - $this->assertEquals([$item], $unit->items()); + $this->assertEquals([$this->item], $unit->items()); $this->assertEquals($amount, $unit->amount()); $this->assertEquals($shipping, $unit->shipping()); } + public function testWcOrderWithNegativeFees() + { + $wcOrder = Mockery::mock(\WC_Order::class); + $wcOrder->expects('get_order_number')->andReturn($this->wcOrderNumber); + $wcOrder->expects('get_id')->andReturn($this->wcOrderId); + $amount = Mockery::mock(Amount::class); + $amountFactory = Mockery::mock(AmountFactory::class); + $amountFactory + ->shouldReceive('from_wc_order') + ->with($wcOrder) + ->andReturn($amount); + $payeeFactory = Mockery::mock(PayeeFactory::class); + $payeeRepository = Mockery::mock(PayeeRepository::class); + $payee = Mockery::mock(Payee::class); + $payeeRepository + ->shouldReceive('payee')->andReturn($payee); + + $fee = Mockery::mock(Item::class, [ + 'category' => Item::DIGITAL_GOODS, + 'unit_amount' => new Money(10.0, 'USD'), + ]); + $discount = Mockery::mock(Item::class, [ + 'unit_amount' => new Money(-5, 'USD'), + ]); + + $itemFactory = Mockery::mock(ItemFactory::class); + $itemFactory + ->shouldReceive('from_wc_order') + ->with($wcOrder) + ->andReturn([$this->item, $fee, $discount]); + + $address = Mockery::mock(Address::class); + $address->shouldReceive('country_code')->andReturn('DE'); + $address->shouldReceive('postal_code')->andReturn('12345'); + $shipping = Mockery::mock(Shipping::class); + $shipping->shouldReceive('address')->andReturn($address); + $shippingFactory = Mockery::mock(ShippingFactory::class); + $shippingFactory + ->shouldReceive('from_wc_order') + ->with($wcOrder) + ->andReturn($shipping); + $paymentsFacory = Mockery::mock(PaymentsFactory::class); + $testee = new PurchaseUnitFactory( + $amountFactory, + $payeeRepository, + $payeeFactory, + $itemFactory, + $shippingFactory, + $paymentsFacory + ); + + $unit = $testee->from_wc_order($wcOrder); + $this->assertTrue(is_a($unit, PurchaseUnit::class)); + $this->assertEquals([$this->item, $fee], $unit->items()); + } + public function testWcOrderShippingGetsDroppedWhenNoPostalCode() { $wcOrder = Mockery::mock(\WC_Order::class); @@ -100,12 +167,11 @@ class PurchaseUnitFactoryTest extends TestCase $payee = Mockery::mock(Payee::class); $payeeRepository ->expects('payee')->andReturn($payee); - $item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]); $itemFactory = Mockery::mock(ItemFactory::class); $itemFactory ->expects('from_wc_order') ->with($wcOrder) - ->andReturn([$item]); + ->andReturn([$this->item]); $address = Mockery::mock(Address::class); $address @@ -155,12 +221,11 @@ class PurchaseUnitFactoryTest extends TestCase $payee = Mockery::mock(Payee::class); $payeeRepository ->expects('payee')->andReturn($payee); - $item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]); $itemFactory = Mockery::mock(ItemFactory::class); $itemFactory ->expects('from_wc_order') ->with($wcOrder) - ->andReturn([$item]); + ->andReturn([$this->item]); $address = Mockery::mock(Address::class); $address @@ -208,13 +273,11 @@ class PurchaseUnitFactoryTest extends TestCase $payeeRepository ->expects('payee')->andReturn($payee); - $item = Mockery::mock(Item::class); - $item->shouldReceive('category')->andReturn(Item::PHYSICAL_GOODS); $itemFactory = Mockery::mock(ItemFactory::class); $itemFactory ->expects('from_wc_cart') ->with($wcCart) - ->andReturn([$item]); + ->andReturn([$this->item]); $address = Mockery::mock(Address::class); $address @@ -251,7 +314,7 @@ class PurchaseUnitFactoryTest extends TestCase $this->assertEquals('', $unit->custom_id()); $this->assertEquals('', $unit->soft_descriptor()); $this->assertEquals('', $unit->invoice_id()); - $this->assertEquals([$item], $unit->items()); + $this->assertEquals([$this->item], $unit->items()); $this->assertEquals($amount, $unit->amount()); $this->assertEquals($shipping, $unit->shipping()); } @@ -273,13 +336,12 @@ class PurchaseUnitFactoryTest extends TestCase $payee = Mockery::mock(Payee::class); $payeeRepository ->expects('payee')->andReturn($payee); - $item = Mockery::mock(Item::class); - $item->shouldReceive('category')->andReturn(Item::PHYSICAL_GOODS); + $itemFactory = Mockery::mock(ItemFactory::class); $itemFactory ->expects('from_wc_cart') ->with($wcCart) - ->andReturn([$item]); + ->andReturn([$this->item]); $shippingFactory = Mockery::mock(ShippingFactory::class); $paymentsFacory = Mockery::mock(PaymentsFactory::class); $testee = new PurchaseUnitFactory( @@ -312,12 +374,11 @@ class PurchaseUnitFactoryTest extends TestCase $payee = Mockery::mock(Payee::class); $payeeRepository ->expects('payee')->andReturn($payee); - $item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]); $itemFactory = Mockery::mock(ItemFactory::class); $itemFactory ->expects('from_wc_cart') ->with($wcCart) - ->andReturn([$item]); + ->andReturn([$this->item]); $address = Mockery::mock(Address::class); $address @@ -359,8 +420,7 @@ class PurchaseUnitFactoryTest extends TestCase $payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee); $payeeRepository = Mockery::mock(PayeeRepository::class); $itemFactory = Mockery::mock(ItemFactory::class); - $item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]); - $itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($item); + $itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item); $shippingFactory = Mockery::mock(ShippingFactory::class); $shipping = Mockery::mock(Shipping::class); $shippingFactory->expects('from_paypal_response')->with($rawShipping)->andReturn($shipping); @@ -394,7 +454,7 @@ class PurchaseUnitFactoryTest extends TestCase $this->assertEquals('customId', $unit->custom_id()); $this->assertEquals('softDescriptor', $unit->soft_descriptor()); $this->assertEquals('invoiceId', $unit->invoice_id()); - $this->assertEquals([$item], $unit->items()); + $this->assertEquals([$this->item], $unit->items()); $this->assertEquals($amount, $unit->amount()); $this->assertEquals($shipping, $unit->shipping()); } @@ -411,8 +471,7 @@ class PurchaseUnitFactoryTest extends TestCase $payeeFactory = Mockery::mock(PayeeFactory::class); $payeeRepository = Mockery::mock(PayeeRepository::class); $itemFactory = Mockery::mock(ItemFactory::class); - $item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]); - $itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($item); + $itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item); $shippingFactory = Mockery::mock(ShippingFactory::class); $shipping = Mockery::mock(Shipping::class); $shippingFactory->expects('from_paypal_response')->with($rawShipping)->andReturn($shipping); @@ -454,8 +513,7 @@ class PurchaseUnitFactoryTest extends TestCase $payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee); $payeeRepository = Mockery::mock(PayeeRepository::class); $itemFactory = Mockery::mock(ItemFactory::class); - $item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]); - $itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($item); + $itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item); $shippingFactory = Mockery::mock(ShippingFactory::class); $paymentsFacory = Mockery::mock(PaymentsFactory::class); $testee = new PurchaseUnitFactory( From a4bf56021dc6d6c086c9a0039d44da11a2610bdc Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 16 May 2022 16:49:09 +0300 Subject: [PATCH 44/44] Download the specified WP version in DDEV This parameter was not passed to wp core download --- .ddev/commands/web/orchestrate.d/00_download_wordpress.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ddev/commands/web/orchestrate.d/00_download_wordpress.sh b/.ddev/commands/web/orchestrate.d/00_download_wordpress.sh index 6ff769fd3..b6f261f30 100644 --- a/.ddev/commands/web/orchestrate.d/00_download_wordpress.sh +++ b/.ddev/commands/web/orchestrate.d/00_download_wordpress.sh @@ -1,6 +1,6 @@ #!/bin/bash -if ! wp core download; then +if ! wp core download --version="${WP_VERSION:-latest}"; then echo 'WordPress is already installed.' exit fi