From 5e2094aa2bc595dcf9c7471f1c1d5c4562c6650d Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 3 Jan 2022 13:13:28 +0000 Subject: [PATCH 01/31] Empty commit for release pull request From da48bc70a5dafd499366689630062f7b11c18d44 Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Mon, 3 Jan 2022 14:30:11 +0100 Subject: [PATCH 02/31] Update readme.txt with 6.7.0 changelog --- readme.txt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/readme.txt b/readme.txt index efb2a6db9d7..bc38f9a1f89 100644 --- a/readme.txt +++ b/readme.txt @@ -85,6 +85,22 @@ Release and roadmap notes available on the [WooCommerce Developers Blog](https:/ == Changelog == += 6.7.0 - 2022-01-03 = + +#### Enhancements + +- Added global styles (text color) to the Active Filters block. ([5465](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5465)) +- Prevent a 0 value shipping price being shown in the Checkout if no shipping methods are available. ([5444](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5444)) + +#### Bug Fixes + +- Fixed an issue where the checkout address fields would be blank for logged in customers. ([5473](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5473)) +- Account for products without variations in the On Sale Products block. ([5470](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5470)) +- Update the template retrieving logic to allow for older Gutenberg convention and newer one (`block-templates`/`block-template-parts` vs. `templates`/`parts`). ([5455](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5455)) +- Ensure that the translation of the "Proceed to Checkout" button is working. ([5453](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5453)) +- Fix custom templates with fallback to archive being incorrectly attributed to the user in the editor instead of the parent theme. ([5447](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5447)) +- Remove text decorations from product filtering blocks items. ([5384](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5384)) + = 6.6.0 - 2021-12-20 = #### Bug Fixes From 4f09253f9cb18c9caf396056a558f3e50a6cb0c4 Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Mon, 3 Jan 2022 15:58:09 +0100 Subject: [PATCH 03/31] Add testing notes for the 6.7.0 release --- docs/testing/releases/670.md | 98 +++++++++++++++++++++++++++++++++ docs/testing/releases/README.md | 1 + 2 files changed, 99 insertions(+) create mode 100644 docs/testing/releases/670.md diff --git a/docs/testing/releases/670.md b/docs/testing/releases/670.md new file mode 100644 index 00000000000..47cfb7dd033 --- /dev/null +++ b/docs/testing/releases/670.md @@ -0,0 +1,98 @@ +## Testing notes and ZIP for release 6.7.0 + +Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github.com/woocommerce/woocommerce-gutenberg-products-block/files/7802539/woocommerce-gutenberg-products-block.zip) + +## Feature Plugin + +### Fixed an issue where the checkout address fields would be blank for logged in customers. ([5473](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5473)) + +1. While logged in, with a customer than has checked out before, visit the Checkout block page +2. Confirm shipping address fields are populated +3. Toggle the `Different billing address' box`. Confirm that the billing fields are populated. +4. Finish your purchase successfully + +### Prevent a 0 value shipping price being shown in the Checkout if no shipping methods are available. ([5444](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5444)) + +| Before | After | +| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| ![image](https://user-images.githubusercontent.com/5656702/147132243-bd42c69c-415b-45d6-9e7f-ebd02e1c0ab9.png) | ![image](https://user-images.githubusercontent.com/5656702/147132055-1efbd023-f99c-4651-bccc-5608ddc7ba7a.png) | + +1. Go to WooCommerce > Settings > Shipping and remove all shipping methods, including shipping methods for `Locations not covered by your other zones`. +2. Add a shipping zone for a specific country, for example USA. +3. Add some methods to this zone, free shipping and flat rate are fine. +4. Add some items to the cart. +5. Go to the Cart. +6. Check you can still use the shipping calculator. +7. Check that the shipping rates show when using a USA address. +8. Check the error shows when using an address from another country, e.g. UK. +9. Go to the Checkout block. +10. Notice the shipping subtotal does not contain a price if your address is in an invalid country. +11. Change country on the Checkout form. +12. Notice that a shipping price is only shown when the address is in a valid country. +13. Try changing country back and forth and selecting different shipping rates. + +### Ensure that the translation of the "Proceed to Checkout" button is working. ([5453](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5453)) + +| Before | After | +| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| ![image](https://user-images.githubusercontent.com/3323310/147332014-9d76312d-a0a4-4682-94fa-5fbed6957380.png) | ![image](https://user-images.githubusercontent.com/3323310/147332020-48140705-4b7f-44e4-b967-0459424a33cd.png) | + +1. Create a test page and add the Cart block. +2. Go to `/wp-admin/options-general.php` and change the site language to `Norsk bokmål`. +3. Go to `/wp-admin/update-core.php` and update the translations. +4. Go to the front-end, add a product to cart and visit the Cart block. +5. See that the "Proceed to Checkout" button is now translated. + +### Added global styles (text color) to the Active Filters block. ([5465](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5465)) + +![image](https://user-images.githubusercontent.com/4463174/147587196-86b1ee61-8ec9-41c3-a9dd-0a709b68e513.png) + +1. Install and enable the `Gutenberg` plugin. +2. Install and enable the `TT1 Blocks` theme. +3. Add the Active Filters block to a post. (you need to add a block like `Filters Product By Attribute`) +4. Verify you can change the text color. +5. Save your changes. +6. Go on the page and check if there are changes. +7. Reset to default. +8. Go to admin panel and click `Site Editor`. +9. Click on the Global styles icon. +10. Verify the Active Filters block is shown and you can tweak its styles. +11. Save your changes. +12. Go on the page created earlier and check if all styles are applied correctly. +13. Edit your previous post/page again. +14. Change again the text color. +15. Save your changes. +16. Check if these styles have priority over the styles from the Site Editor. + +### Fix custom templates with fallback to archive being incorrectly attributed to the user in the editor instead of the parent theme. ([5447](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5447)) + +1. Install and enable the `Gutenberg` plugin. +2. Install and enable the `TT1 Blocks` theme. +3. Add an `archive-product.html`. This is because we are using the themes archive-product.html file for the category and tag templates as they're typically the same. +4. Go to the templates list within the Site Editor +5. You will see now that `Product Archive`, `Product Category` and `Product Tag` are added by the theme. +6. Customize the `Product Category` template and save it. +7. Navigate back to the templates list within the Site Editor. You will notice under the "Added By" column it should say your theme along with a theme icon. +8. Notice that these templates render in the Site Editor, and on the front-end as expected with and without customizations. + +## Feature plugin and package inclusion in WooCommerce + +### Account for products without variations in the On Sale Products block. ([5470](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5470)) + +On an store with no products: + +1. Add 2 simple products on sale and publish them. +2. Go to a test page and add the On Sale Products block. +3. Visit the page on the front-end. +4. Notice that no MySQL error is present in Query Monitor. + +### Remove text decorations from product filtering blocks items. ([5384](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5384)) + +| Before | After | +| -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| ![image](https://user-images.githubusercontent.com/3323310/146129258-794efe6d-b73d-4d33-a7f6-421e0ecaad3f.png) | ![image](https://user-images.githubusercontent.com/3323310/146129264-df7a0531-286c-4316-ab22-5605735d6e26.png) | + +1. Create a test page. +2. Add the Filter Products by Attribute, Filter Products by Stock and All Products blocks to it. +3. Visit the test page in the frontend. +4. Verify that the options within the Filter Products by Attribute and Filter Products by Stock blocks are not underlined. diff --git a/docs/testing/releases/README.md b/docs/testing/releases/README.md index b980aa58bab..85aa3c63e70 100644 --- a/docs/testing/releases/README.md +++ b/docs/testing/releases/README.md @@ -54,3 +54,4 @@ Every release includes specific testing instructions for new features and bug fi - [6.4.0](./640.md) - [6.5.0](./650.md) - [6.6.0](./660.md) +- [6.7.0](./670.md) From a0dfd5cfbacd2fe166a5bcf200f87e943585dcea Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Mon, 3 Jan 2022 15:59:35 +0100 Subject: [PATCH 04/31] Fix typo in testing notes --- docs/testing/releases/670.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/testing/releases/670.md b/docs/testing/releases/670.md index 47cfb7dd033..043ad53b722 100644 --- a/docs/testing/releases/670.md +++ b/docs/testing/releases/670.md @@ -8,7 +8,7 @@ Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github. 1. While logged in, with a customer than has checked out before, visit the Checkout block page 2. Confirm shipping address fields are populated -3. Toggle the `Different billing address' box`. Confirm that the billing fields are populated. +3. Toggle the `Different billing address` box. Confirm that the billing fields are populated. 4. Finish your purchase successfully ### Prevent a 0 value shipping price being shown in the Checkout if no shipping methods are available. ([5444](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5444)) From 4ec29d3b63822612e7c23e4b686d047b558f1918 Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Mon, 3 Jan 2022 17:52:36 +0100 Subject: [PATCH 05/31] Update Testing Notes --- docs/testing/releases/670.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/testing/releases/670.md b/docs/testing/releases/670.md index 043ad53b722..87ebaf88a4b 100644 --- a/docs/testing/releases/670.md +++ b/docs/testing/releases/670.md @@ -40,23 +40,23 @@ Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github. 1. Create a test page and add the Cart block. 2. Go to `/wp-admin/options-general.php` and change the site language to `Norsk bokmål`. 3. Go to `/wp-admin/update-core.php` and update the translations. -4. Go to the front-end, add a product to cart and visit the Cart block. +4. Go to the front-end page, add a product to cart and visit the Cart block. 5. See that the "Proceed to Checkout" button is now translated. -### Added global styles (text color) to the Active Filters block. ([5465](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5465)) +### Added global styles (text color) to the Active Product Filters block. ([5465](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5465)) ![image](https://user-images.githubusercontent.com/4463174/147587196-86b1ee61-8ec9-41c3-a9dd-0a709b68e513.png) 1. Install and enable the `Gutenberg` plugin. 2. Install and enable the `TT1 Blocks` theme. -3. Add the Active Filters block to a post. (you need to add a block like `Filters Product By Attribute`) -4. Verify you can change the text color. +3. Add the Active Product Filters block block to a post. (you need to add a block like `Filters Product By Attribute`) +4. Go to Color section & verify you can change the text color. 5. Save your changes. 6. Go on the page and check if there are changes. -7. Reset to default. -8. Go to admin panel and click `Site Editor`. -9. Click on the Global styles icon. -10. Verify the Active Filters block is shown and you can tweak its styles. +7. Reset to default using the `Clear` button from Styles > Text color window. +8. Go to Dashboard and select Appearance > Editor (beta). +9. On the Editor page click on the `Styles` icon on the right-top corner. +10. Verify that the Active Product Filters block is shown Under the `Blocks` section . 11. Save your changes. 12. Go on the page created earlier and check if all styles are applied correctly. 13. Edit your previous post/page again. @@ -69,22 +69,22 @@ Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github. 1. Install and enable the `Gutenberg` plugin. 2. Install and enable the `TT1 Blocks` theme. 3. Add an `archive-product.html`. This is because we are using the themes archive-product.html file for the category and tag templates as they're typically the same. -4. Go to the templates list within the Site Editor +4. Go to the templates list within the Site Editor (Dashboard > Appearance > Editor (beta)) 5. You will see now that `Product Archive`, `Product Category` and `Product Tag` are added by the theme. 6. Customize the `Product Category` template and save it. 7. Navigate back to the templates list within the Site Editor. You will notice under the "Added By" column it should say your theme along with a theme icon. -8. Notice that these templates render in the Site Editor, and on the front-end as expected with and without customizations. +8. Notice that these templates render in the Site Editor, and on the front-end page as expected with and without customizations. ## Feature plugin and package inclusion in WooCommerce ### Account for products without variations in the On Sale Products block. ([5470](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5470)) -On an store with no products: +On an store with no products add the Query Monitor extension, then: 1. Add 2 simple products on sale and publish them. 2. Go to a test page and add the On Sale Products block. -3. Visit the page on the front-end. -4. Notice that no MySQL error is present in Query Monitor. +3. Visit the front-end page. +4. Notice that no MySQL error is present in Query Monitor extension. ### Remove text decorations from product filtering blocks items. ([5384](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5384)) @@ -94,5 +94,5 @@ On an store with no products: 1. Create a test page. 2. Add the Filter Products by Attribute, Filter Products by Stock and All Products blocks to it. -3. Visit the test page in the frontend. +3. Visit the front-end test page. 4. Verify that the options within the Filter Products by Attribute and Filter Products by Stock blocks are not underlined. From 15e57ab14dad9b994565916902843ccd30f900d1 Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Tue, 4 Jan 2022 10:07:44 +0100 Subject: [PATCH 06/31] Remove item from testing notes for 6.7.0 --- docs/testing/releases/670.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/testing/releases/670.md b/docs/testing/releases/670.md index 87ebaf88a4b..679de923a42 100644 --- a/docs/testing/releases/670.md +++ b/docs/testing/releases/670.md @@ -64,17 +64,6 @@ Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github. 15. Save your changes. 16. Check if these styles have priority over the styles from the Site Editor. -### Fix custom templates with fallback to archive being incorrectly attributed to the user in the editor instead of the parent theme. ([5447](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5447)) - -1. Install and enable the `Gutenberg` plugin. -2. Install and enable the `TT1 Blocks` theme. -3. Add an `archive-product.html`. This is because we are using the themes archive-product.html file for the category and tag templates as they're typically the same. -4. Go to the templates list within the Site Editor (Dashboard > Appearance > Editor (beta)) -5. You will see now that `Product Archive`, `Product Category` and `Product Tag` are added by the theme. -6. Customize the `Product Category` template and save it. -7. Navigate back to the templates list within the Site Editor. You will notice under the "Added By" column it should say your theme along with a theme icon. -8. Notice that these templates render in the Site Editor, and on the front-end page as expected with and without customizations. - ## Feature plugin and package inclusion in WooCommerce ### Account for products without variations in the On Sale Products block. ([5470](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5470)) From 8807d690551a4afb0c05b1f1a08db4ae7bb688bd Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Tue, 4 Jan 2022 11:47:59 +0100 Subject: [PATCH 07/31] Bumping version strings to new version. --- package.json | 2 +- readme.txt | 2 +- src/Package.php | 2 +- woocommerce-gutenberg-products-block.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6d4571802da..8ca10584af9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/block-library", "title": "WooCommerce Blocks", "author": "Automattic", - "version": "6.7.0-dev", + "version": "6.7.0", "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/", "keywords": [ diff --git a/readme.txt b/readme.txt index bc38f9a1f89..f0d6a942ef4 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: gutenberg, woocommerce, woo commerce, products, blocks, woocommerce blocks Requires at least: 5.8 Tested up to: 5.8 Requires PHP: 7.0 -Stable tag: 6.7.0-dev +Stable tag: 6.7.0 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/src/Package.php b/src/Package.php index 652922448f0..386fbe2da41 100644 --- a/src/Package.php +++ b/src/Package.php @@ -106,7 +106,7 @@ public static function container( $reset = false ) { NewPackage::class, function ( $container ) { // leave for automated version bumping. - $version = '6.7.0-dev'; + $version = '6.7.0'; return new NewPackage( $version, dirname( __DIR__ ), diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php index 4eacc006253..cbb61d56fcc 100644 --- a/woocommerce-gutenberg-products-block.php +++ b/woocommerce-gutenberg-products-block.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce Blocks * Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block * Description: WooCommerce blocks for the Gutenberg editor. - * Version: 6.7.0-dev + * Version: 6.7.0 * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woo-gutenberg-products-block From b81c221214ba30f68d6e2ef4e44345dd18e8f8ef Mon Sep 17 00:00:00 2001 From: Luigi Teschio Date: Fri, 7 Jan 2022 10:47:48 +0100 Subject: [PATCH 08/31] Add wide and full alignment support for legacy template block (#5433) * add align wide and full support for legacy template block * fix PHP warning * add a comment on get_markup_with_classes_by_attributes * rename function * add align wide and full support for legacy template block * fix PHP warning * add a comment on get_markup_with_classes_by_attributes * rename function * fix regex * update regex * update regex * fix code styling --- assets/js/blocks/legacy-template/index.tsx | 2 +- src/BlockTypes/LegacyTemplate.php | 49 ++++++++++++++++++++ src/Utils/StyleAttributesUtils.php | 53 ++++++++++++++++++++++ 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/assets/js/blocks/legacy-template/index.tsx b/assets/js/blocks/legacy-template/index.tsx index fe973e65f10..c37165d5be3 100644 --- a/assets/js/blocks/legacy-template/index.tsx +++ b/assets/js/blocks/legacy-template/index.tsx @@ -82,7 +82,7 @@ registerBlockType( 'woocommerce/legacy-template', { 'woo-gutenberg-products-block' ), supports: { - align: false, + align: [ 'wide', 'full' ], html: false, multiple: false, reusable: false, diff --git a/src/BlockTypes/LegacyTemplate.php b/src/BlockTypes/LegacyTemplate.php index 38980836fdb..a9c0ce6a9b6 100644 --- a/src/BlockTypes/LegacyTemplate.php +++ b/src/BlockTypes/LegacyTemplate.php @@ -1,6 +1,8 @@ block_name ) !== $block['blockName'] ) { + return $content; + } + + $attributes = (array) $block['attrs']; + $align_class_and_style = StyleAttributesUtils::get_align_class_and_style( $attributes ); + + if ( ! isset( $align_class_and_style['class'] ) ) { + return $content; + } + + // Find the first tag. + $first_tag = '<[^<>]+>'; + $matches = array(); + preg_match( $first_tag, $content, $matches ); + + // If there is a tag, but it doesn't have a class attribute, add the class attribute. + if ( isset( $matches[0] ) && strpos( $matches[0], ' class=' ) === false ) { + $pattern_before_tag_closing = '/.+?(?=>)/'; + return preg_replace( $pattern_before_tag_closing, '$0 class="' . $align_class_and_style['class'] . '"', $content, 1 ); + } + + // If there is a tag, and it has a class already, add the class attribute. + $pattern_get_class = '/(?<=class=\"|\')[^"|\']+(?=\"|\')/'; + return preg_replace( $pattern_get_class, '$0 ' . $align_class_and_style['class'], $content, 1 ); + } + + } diff --git a/src/Utils/StyleAttributesUtils.php b/src/Utils/StyleAttributesUtils.php index dbf1f40666b..08ba95f0e6c 100644 --- a/src/Utils/StyleAttributesUtils.php +++ b/src/Utils/StyleAttributesUtils.php @@ -154,6 +154,59 @@ public static function get_background_color_class_and_style( $attributes ) { return null; } + /** + * Get class and style for align from attributes. + * + * @param array $attributes Block attributes. + * + * @return (array | null) + */ + public static function get_align_class_and_style( $attributes ) { + + $align_attribute = isset( $attributes['align'] ) ? $attributes['align'] : null; + + if ( ! $align_attribute ) { + return null; + }; + + if ( 'wide' === $align_attribute ) { + return array( + 'class' => 'alignwide', + 'style' => null, + ); + } + + if ( 'full' === $align_attribute ) { + return array( + 'class' => 'alignfull', + 'style' => null, + ); + } + + if ( 'left' === $align_attribute ) { + return array( + 'class' => 'alignleft', + 'style' => null, + ); + } + + if ( 'right' === $align_attribute ) { + return array( + 'class' => 'alignright', + 'style' => null, + ); + } + + if ( 'center' === $align_attribute ) { + return array( + 'class' => 'aligncenter', + 'style' => null, + ); + } + + return null; + } + /** * Get classes and styles from attributes. * From e2207271f11850507ed8eec8a77fe35e80af01e0 Mon Sep 17 00:00:00 2001 From: Tom Cafferkey Date: Fri, 7 Jan 2022 10:17:08 +0000 Subject: [PATCH 09/31] Query legacy and correct plugin slug for WooCommerce block templates (#5519) --- src/BlockTemplatesController.php | 13 ++++++++++--- src/Utils/BlockTemplateUtils.php | 24 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/src/BlockTemplatesController.php b/src/BlockTemplatesController.php index 7ea5ca9ce1a..11ed5eace06 100644 --- a/src/BlockTemplatesController.php +++ b/src/BlockTemplatesController.php @@ -100,8 +100,8 @@ public function maybe_return_blocks_template( $template, $id, $template_type ) { // been unhooked so won't run again. add_filter( 'get_block_file_template', array( $this, 'get_single_block_template' ), 10, 3 ); $maybe_template = function_exists( 'gutenberg_get_block_template' ) ? - gutenberg_get_block_template( 'woocommerce//' . $slug, $template_type ) : - get_block_template( 'woocommerce//' . $slug, $template_type ); + gutenberg_get_block_template( BlockTemplateUtils::PLUGIN_SLUG . '//' . $slug, $template_type ) : + get_block_template( BlockTemplateUtils::PLUGIN_SLUG . '//' . $slug, $template_type ); // Re-hook this function, it was only unhooked to stop recursion. add_filter( 'pre_get_block_file_template', array( $this, 'maybe_return_blocks_template' ), 10, 3 ); @@ -265,6 +265,11 @@ function( $template ) use ( $customised_template_slugs ) { * @return int[]|\WP_Post[] An array of found templates. */ public function get_block_templates_from_db( $slugs = array(), $template_type = 'wp_template' ) { + // This was the previously incorrect slug used to save DB templates against. + // To maintain compatibility with users sites who have already customised WooCommerce block templates using this slug we have to still use it to query those. + // More context found here: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423. + $invalid_plugin_slug = 'woocommerce'; + $check_query_args = array( 'post_type' => $template_type, 'posts_per_page' => -1, @@ -273,13 +278,15 @@ public function get_block_templates_from_db( $slugs = array(), $template_type = array( 'taxonomy' => 'wp_theme', 'field' => 'name', - 'terms' => array( 'woocommerce', get_stylesheet() ), + 'terms' => array( $invalid_plugin_slug, BlockTemplateUtils::PLUGIN_SLUG, get_stylesheet() ), ), ), ); + if ( is_array( $slugs ) && count( $slugs ) > 0 ) { $check_query_args['post_name__in'] = $slugs; } + $check_query = new \WP_Query( $check_query_args ); $saved_woo_templates = $check_query->posts; diff --git a/src/Utils/BlockTemplateUtils.php b/src/Utils/BlockTemplateUtils.php index fc50cd059b9..4e8414f489a 100644 --- a/src/Utils/BlockTemplateUtils.php +++ b/src/Utils/BlockTemplateUtils.php @@ -27,6 +27,15 @@ class BlockTemplateUtils { 'TEMPLATE_PARTS' => 'parts', ); + /** + * WooCommerce plugin slug + * + * This is used to save templates to the DB which are stored against this value in the wp_terms table. + * + * @var string + */ + const PLUGIN_SLUG = 'woocommerce/woocommerce'; + /** * Returns an array containing the references of * the passed blocks and their inner blocks. @@ -119,7 +128,7 @@ public static function gutenberg_build_template_result_from_post( $post ) { $template = new \WP_Block_Template(); $template->wp_id = $post->ID; $template->id = $theme . '//' . $post->post_name; - $template->theme = 'woocommerce' === $theme ? 'WooCommerce' : $theme; + $template->theme = $theme; $template->content = $post->post_content; $template->slug = $post->post_name; $template->source = 'custom'; @@ -138,7 +147,10 @@ public static function gutenberg_build_template_result_from_post( $post ) { } } - if ( 'woocommerce' === $theme ) { + // We are checking 'woocommerce' to maintain legacy templates which are saved to the DB, + // prior to updating to use the correct slug. + // More information found here: https://github.com/woocommerce/woocommerce-gutenberg-products-block/issues/5423. + if ( self::PLUGIN_SLUG === $theme || 'woocommerce' === strtolower( $theme ) ) { $template->origin = 'plugin'; } @@ -164,8 +176,8 @@ public static function gutenberg_build_template_result_from_file( $template_file // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $template_content = file_get_contents( $template_file->path ); $template = new \WP_Block_Template(); - $template->id = $template_is_from_theme ? $theme_name . '//' . $template_file->slug : 'woocommerce//' . $template_file->slug; - $template->theme = $template_is_from_theme ? $theme_name : 'WooCommerce'; + $template->id = $template_is_from_theme ? $theme_name . '//' . $template_file->slug : self::PLUGIN_SLUG . '//' . $template_file->slug; + $template->theme = $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG; $template->content = self::gutenberg_inject_theme_attribute_in_content( $template_content ); // Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909. $template->source = $template_file->source ? $template_file->source : 'plugin'; @@ -196,10 +208,10 @@ public static function create_new_block_template_object( $template_file, $templa $new_template_item = array( 'slug' => $template_slug, - 'id' => $template_is_from_theme ? $theme_name . '//' . $template_slug : 'woocommerce//' . $template_slug, + 'id' => $template_is_from_theme ? $theme_name . '//' . $template_slug : self::PLUGIN_SLUG . '//' . $template_slug, 'path' => $template_file, 'type' => $template_type, - 'theme' => $template_is_from_theme ? $theme_name : 'woocommerce', + 'theme' => $template_is_from_theme ? $theme_name : self::PLUGIN_SLUG, // Plugin was agreed as a valid source value despite existing inline docs at the time of creating: https://github.com/WordPress/gutenberg/issues/36597#issuecomment-976232909. 'source' => $template_is_from_theme ? 'theme' : 'plugin', 'title' => self::convert_slug_to_title( $template_slug ), From c9ffd69d87997422f65d621cb6f107fbe9ebd6b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Albert=20Juh=C3=A9=20Lluveras?= Date: Fri, 7 Jan 2022 13:40:41 +0100 Subject: [PATCH 10/31] Update PR assignment automation to match new Team Kirigami name (#5534) --- .github/automate-team-review-assignment-config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/automate-team-review-assignment-config.yml b/.github/automate-team-review-assignment-config.yml index 21f21d82aa8..c6568b8df6b 100644 --- a/.github/automate-team-review-assignment-config.yml +++ b/.github/automate-team-review-assignment-config.yml @@ -9,9 +9,9 @@ when: - rubik-fp-squad - author: teamIs: - - rubik-fse-squad + - kirigami ignore: nameIs: assign: teams: - - rubik-fse-squad + - kirigami From fa88c303a77266688b48a557f7f03de7f7070c24 Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Fri, 7 Jan 2022 14:41:25 +0100 Subject: [PATCH 11/31] Force a string token property for the activePaymentMethod (#5535) --- .../payment-methods/payment-method-data-context.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx b/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx index 3fe19ab4f60..d760dbd9f3a 100644 --- a/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx +++ b/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx @@ -195,14 +195,14 @@ export const PaymentMethodDataProvider = ( { )[ 0 ] || undefined; if ( customerPaymentMethod ) { - const token = customerPaymentMethod.tokenId; + const token = customerPaymentMethod.tokenId.toString(); const paymentMethodSlug = customerPaymentMethod.method.gateway; const savedTokenKey = `wc-${ paymentMethodSlug }-payment-token`; dispatchActions.setActivePaymentMethod( paymentMethodSlug, { token, payment_method: paymentMethodSlug, - [ savedTokenKey ]: token.toString(), + [ savedTokenKey ]: token, isSavedToken: true, } ); return; From 3b1cdb1999c771182855b8dc7122ff426f8b45e2 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 7 Jan 2022 15:07:56 +0000 Subject: [PATCH 12/31] Empty commit for release pull request From 6c28474cbf66392076ff051542ec979222ca41e4 Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Fri, 7 Jan 2022 17:46:25 +0100 Subject: [PATCH 13/31] Update release initial checklist template (#5526) * Update release initial checklist template This PR includes more information about updating the Woo Blocks version in the WooCommerce repository: - it updates the PR used as an example - it updates the url for the composer.json file in WC repo - it add info about how to update the composer.json and lock files * Update .github/release-initial-checklist.md --- .github/release-initial-checklist.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/release-initial-checklist.md b/.github/release-initial-checklist.md index 6aea7d34794..bfae845eb2a 100644 --- a/.github/release-initial-checklist.md +++ b/.github/release-initial-checklist.md @@ -98,9 +98,9 @@ Additionally, make sure to differentiate between things in the testing notes tha This only needs to be done if this release is the last release of the feature plugin before code freeze in the WooCommerce core cycle. If this condition doesn't exist you can skip this section. * [ ] Remind whoever is porter this week to audit our codebase to ensure this [experimental interface document](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/blocks/feature-flags-and-experimental-interfaces.md) is up to date. See Pca54o-rM-p2 for more details. -* [ ] Create a pull request for updating the package in the [WooCommerce Core Repository](https://github.com/woocommerce/woocommerce/) that [bumps the package version](https://github.com/woocommerce/woocommerce/blob/master/composer.json) for the blocks package to the version being pulled in. - * The content for the pull release can follow [this example](https://github.com/woocommerce/woocommerce/pull/27676). Essentially you link to all the important things that have already been prepared. Note, you need to make sure you link to all the related documents for the feature plugin releases since the last package version bump in Woo Core. - * Please add a changelog to the content which is aggregated from all the releases included in the package bump. The changelog should only list things surfaced to users of the package in WooCommerce core (i.e. excluding things only available in the feature plugin or development builds). This changelog will be used in the release notes for the WooCommerce release. **Note: This currently is not shown in the linked example.** +* [ ] Create a pull request for updating the package in the [WooCommerce Core Repository](https://github.com/woocommerce/woocommerce/) that [bumps the package version](https://github.com/woocommerce/woocommerce/blob/747cb6b7184ba9fdc875ab104da5839cfda8b4be/plugins/woocommerce/composer.json) for the Woo Blocks package to the version being pulled in. + * The content for the pull release can follow [this example](https://github.com/woocommerce/woocommerce/pull/31556). Update the `plugins/woocommerce/composer.json` file and then run `composer update`. In the PR description you will link to all the important things that have already been prepared since the version you replaced. Note, you need to make sure you link to all the related documents for the plugin releases since the last package version bump in Woo Core. + * Please add a changelog to the content which is aggregated from all the releases included in the package bump. The changelog should only list things surfaced to users of the package in WooCommerce core (i.e. excluding things only available in the feature plugin or development builds). This changelog will be used in the release notes for the WooCommerce release. * Run through the testing checklist to ensure everything works in that branch for that package bump. **Note:** Testing should include ensuring any features/new blocks that are supposed to be behind feature gating for the core merge of this package update are working as expected. * Testing should include completing the [Smoke testing checklist](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/trunk/docs/testing/smoke-testing.md). It's up to you to verify that those tests have been done. * Verify and make any additional edits to the pull request description for things like: Changelog to be included with WooCommerce core, additional communication that might be needed elsewhere, additional marketing communication notes that may be needed etc. From 66f13f17b6973441cfa0fa26a44f941395b44b82 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Jan 2022 18:00:35 +0100 Subject: [PATCH 14/31] Release: 6.7.1 (#5536) * Force a string token property for the activePaymentMethod * Empty commit for release pull request * Update readme.txt with 6.7.1 info * Add testing notes for 6.7.1 * Bumping version strings to new version. Co-authored-by: Raluca Stan Co-authored-by: github-actions --- .../payment-method-data-context.tsx | 4 ++-- docs/testing/releases/671.md | 15 +++++++++++++++ docs/testing/releases/README.md | 1 + package.json | 2 +- readme.txt | 8 +++++++- src/Package.php | 2 +- woocommerce-gutenberg-products-block.php | 2 +- 7 files changed, 28 insertions(+), 6 deletions(-) create mode 100644 docs/testing/releases/671.md diff --git a/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx b/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx index 3fe19ab4f60..d760dbd9f3a 100644 --- a/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx +++ b/assets/js/base/context/providers/cart-checkout/payment-methods/payment-method-data-context.tsx @@ -195,14 +195,14 @@ export const PaymentMethodDataProvider = ( { )[ 0 ] || undefined; if ( customerPaymentMethod ) { - const token = customerPaymentMethod.tokenId; + const token = customerPaymentMethod.tokenId.toString(); const paymentMethodSlug = customerPaymentMethod.method.gateway; const savedTokenKey = `wc-${ paymentMethodSlug }-payment-token`; dispatchActions.setActivePaymentMethod( paymentMethodSlug, { token, payment_method: paymentMethodSlug, - [ savedTokenKey ]: token.toString(), + [ savedTokenKey ]: token, isSavedToken: true, } ); return; diff --git a/docs/testing/releases/671.md b/docs/testing/releases/671.md new file mode 100644 index 00000000000..1c7b773d9d4 --- /dev/null +++ b/docs/testing/releases/671.md @@ -0,0 +1,15 @@ +## Testing notes and ZIP for release 6.7.1 + +Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github.com/woocommerce/woocommerce-gutenberg-products-block/files/7829419/woocommerce-gutenberg-products-block.zip) + +## Feature Plugin + +### Convert token to string when setting the active payment method. ([5535](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5535)) + +1. On your site make sure you have Stripe set up +2. Create a new customer user for your website +3. Login with that user and add a product to the Cart block +4. In the Checkout block pay with a Stripe card and select Save payment information to my account for future purchases. +5. Successfully place the order +6. Do another purchase and make sure to select the saved card as a payment method +7. Notice that you can successfully place the order and no `payment_data[0][value] is not of type string.boolean` error is showed. diff --git a/docs/testing/releases/README.md b/docs/testing/releases/README.md index 85aa3c63e70..64754e18a89 100644 --- a/docs/testing/releases/README.md +++ b/docs/testing/releases/README.md @@ -55,3 +55,4 @@ Every release includes specific testing instructions for new features and bug fi - [6.5.0](./650.md) - [6.6.0](./660.md) - [6.7.0](./670.md) +- [6.7.1](./671.md) diff --git a/package.json b/package.json index 8ca10584af9..a4dde3a4d50 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/block-library", "title": "WooCommerce Blocks", "author": "Automattic", - "version": "6.7.0", + "version": "6.7.1", "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/", "keywords": [ diff --git a/readme.txt b/readme.txt index f0d6a942ef4..d4bbadeb86b 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: gutenberg, woocommerce, woo commerce, products, blocks, woocommerce blocks Requires at least: 5.8 Tested up to: 5.8 Requires PHP: 7.0 -Stable tag: 6.7.0 +Stable tag: 6.7.1 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -85,6 +85,12 @@ Release and roadmap notes available on the [WooCommerce Developers Blog](https:/ == Changelog == += 6.7.1 - 2022-01-07 = + +#### Bug Fixes + +- Convert token to string when setting the active payment method. ([5535](https://github.com/woocommerce/woocommerce-gutenberg-products-block/pull/5535)) + = 6.7.0 - 2022-01-03 = #### Enhancements diff --git a/src/Package.php b/src/Package.php index 386fbe2da41..a403a24c431 100644 --- a/src/Package.php +++ b/src/Package.php @@ -106,7 +106,7 @@ public static function container( $reset = false ) { NewPackage::class, function ( $container ) { // leave for automated version bumping. - $version = '6.7.0'; + $version = '6.7.1'; return new NewPackage( $version, dirname( __DIR__ ), diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php index cbb61d56fcc..1848a507011 100644 --- a/woocommerce-gutenberg-products-block.php +++ b/woocommerce-gutenberg-products-block.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce Blocks * Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block * Description: WooCommerce blocks for the Gutenberg editor. - * Version: 6.7.0 + * Version: 6.7.1 * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woo-gutenberg-products-block From dbd5f98bb34c37790e8cfb2c906134382447760c Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Fri, 7 Jan 2022 18:13:49 +0100 Subject: [PATCH 15/31] Add back 6.8.0-dev version after 6.7.1 patch --- package.json | 2 +- readme.txt | 2 +- src/Package.php | 2 +- woocommerce-gutenberg-products-block.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 6045b4325ff..cee251a61fb 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/block-library", "title": "WooCommerce Blocks", "author": "Automattic", - "version": "6.7.1", + "version": "6.8.0-dev", "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/", "keywords": [ diff --git a/readme.txt b/readme.txt index d4bbadeb86b..7c6621967f0 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: gutenberg, woocommerce, woo commerce, products, blocks, woocommerce blocks Requires at least: 5.8 Tested up to: 5.8 Requires PHP: 7.0 -Stable tag: 6.7.1 +Stable tag: 6.8.0-dev License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/src/Package.php b/src/Package.php index a403a24c431..c6968c344f6 100644 --- a/src/Package.php +++ b/src/Package.php @@ -106,7 +106,7 @@ public static function container( $reset = false ) { NewPackage::class, function ( $container ) { // leave for automated version bumping. - $version = '6.7.1'; + $version = '6.8.0-dev'; return new NewPackage( $version, dirname( __DIR__ ), diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php index 1848a507011..5cf3ec2123b 100644 --- a/woocommerce-gutenberg-products-block.php +++ b/woocommerce-gutenberg-products-block.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce Blocks * Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block * Description: WooCommerce blocks for the Gutenberg editor. - * Version: 6.7.1 + * Version: 6.8.0-dev * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woo-gutenberg-products-block From 8475bb8c47499ad535aceb4391d9bafa30ec3228 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Sat, 8 Jan 2022 07:51:20 +0700 Subject: [PATCH 16/31] Allow inserting blocks to filled mini cart (#5527) --- .../cart-checkout/mini-cart-contents/edit.tsx | 4 +-- .../mini-cart-contents/editor.scss | 23 ++++++++++-- .../mini-cart-contents/index.tsx | 1 - .../filled-mini-cart-contents-block/edit.tsx | 2 +- .../mini-cart-contents/inner-blocks/index.tsx | 1 + .../mini-cart-footer-block/edit.tsx | 4 +++ .../mini-cart-footer-block/index.tsx | 3 +- .../mini-cart-items-block/block.json | 26 ++++++++++++++ .../mini-cart-items-block/edit.tsx | 35 +++++++++++++++++++ .../mini-cart-items-block/frontend.tsx | 5 +++ .../mini-cart-items-block/index.tsx | 24 +++++++++++++ .../mini-cart-products-table-block/block.json | 4 +-- .../mini-cart-products-table-block/block.tsx | 2 +- .../mini-cart-products-table-block/edit.tsx | 4 +++ .../mini-cart-products-table-block/index.tsx | 3 +- .../mini-cart-title-block/edit.tsx | 4 +++ .../mini-cart-title-block/index.tsx | 3 +- .../inner-blocks/register-components.ts | 10 ++++++ .../blocks/cart-checkout/mini-cart/style.scss | 15 ++++++-- packages/checkout/blocks-registry/types.ts | 1 + templates/parts/mini-cart.html | 19 ++++++++-- 21 files changed, 173 insertions(+), 20 deletions(-) create mode 100644 assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/block.json create mode 100644 assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/edit.tsx create mode 100644 assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/frontend.tsx create mode 100644 assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/index.tsx diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/edit.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/edit.tsx index e3bd8d729e6..48e4cd9eb68 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/edit.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/edit.tsx @@ -63,9 +63,7 @@ const Edit = ( { clientId }: Props ): ReactElement => { return (
- - { ViewSwitcherComponent } - + { ViewSwitcherComponent } .block-editor-inner-blocks > .block-editor-block-list__layout { + display: flex; + flex-direction: column; + min-height: 100vh; +} + +.wp-block-woocommerce-mini-cart-items-block { + display: grid; + flex-grow: 1; + margin-bottom: $gap; + padding: 0 $gap; + + > .block-editor-inner-blocks > .block-editor-block-list__layout { display: flex; flex-direction: column; - min-height: 100vh; + height: 100%; } } .wp-block-woocommerce-mini-cart-products-table-block { margin-bottom: auto; + margin-top: $gap; +} + +.editor-styles-wrapper h2.wc-block-mini-cart__title { + @include font-size(larger); + margin: $gap-largest $gap 0; } diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/index.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/index.tsx index c1d7171645a..ff09c1ba49e 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/index.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/index.tsx @@ -35,7 +35,6 @@ const settings = { multiple: false, reusable: false, inserter: false, - __experimentalExposeControlsToChildren: true, color: { text: false, }, diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/filled-mini-cart-contents-block/edit.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/filled-mini-cart-contents-block/edit.tsx index 69a1de2f6d0..35d6c3172ca 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/filled-mini-cart-contents-block/edit.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/filled-mini-cart-contents-block/edit.tsx @@ -19,7 +19,7 @@ export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => { const defaultTemplate = ( [ [ 'woocommerce/mini-cart-title-block', {} ], - [ 'woocommerce/mini-cart-products-table-block', {} ], + [ 'woocommerce/mini-cart-items-block', {} ], [ 'woocommerce/mini-cart-footer-block', {} ], ].filter( Boolean ) as unknown ) as TemplateArray; diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/index.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/index.tsx index 4cbeef3685f..dd59141ce66 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/index.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/index.tsx @@ -4,5 +4,6 @@ import './empty-mini-cart-contents-block'; import './filled-mini-cart-contents-block'; import './mini-cart-title-block'; +import './mini-cart-items-block'; import './mini-cart-products-table-block'; import './mini-cart-footer-block'; diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/edit.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/edit.tsx index fec6dbc680e..94bae81acb8 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/edit.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/edit.tsx @@ -20,3 +20,7 @@ export const Edit = (): JSX.Element => {
); }; + +export const Save = (): JSX.Element => { + return
; +}; diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/index.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/index.tsx index 44b3d0cbd9c..7750f35e753 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/index.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-footer-block/index.tsx @@ -7,7 +7,7 @@ import { registerFeaturePluginBlockType } from '@woocommerce/block-settings'; /** * Internal dependencies */ -import { Edit } from './edit'; +import { Edit, Save } from './edit'; import metadata from './block.json'; registerFeaturePluginBlockType( metadata, { @@ -20,4 +20,5 @@ registerFeaturePluginBlockType( metadata, { ), }, edit: Edit, + save: Save, } ); diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/block.json b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/block.json new file mode 100644 index 00000000000..d5fc6764c66 --- /dev/null +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/block.json @@ -0,0 +1,26 @@ +{ + "name": "woocommerce/mini-cart-items-block", + "version": "1.0.0", + "title": "Mini Cart Items", + "description": "Contains the products table and other custom blocks of filled mini cart.", + "category": "woocommerce", + "supports": { + "align": false, + "html": false, + "multiple": false, + "reusable": false, + "inserter": false + }, + "attributes": { + "lock": { + "type": "object", + "default": { + "remove": true, + "move": true + } + } + }, + "parent": [ "woocommerce/filled-mini-cart-contents-block" ], + "textdomain": "woo-gutenberg-products-block", + "apiVersion": 2 +} diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/edit.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/edit.tsx new file mode 100644 index 00000000000..3bf91b34215 --- /dev/null +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/edit.tsx @@ -0,0 +1,35 @@ +/** + * External dependencies + */ +import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; +import type { TemplateArray } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ + +export const Edit = (): JSX.Element => { + const blockProps = useBlockProps(); + + const defaultTemplate = ( [ + [ 'woocommerce/mini-cart-products-table-block', {} ], + ].filter( Boolean ) as unknown ) as TemplateArray; + + return ( +
+ +
+ ); +}; + +export const Save = (): JSX.Element => { + return ( +
+ +
+ ); +}; diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/frontend.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/frontend.tsx new file mode 100644 index 00000000000..18788aaef9a --- /dev/null +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/frontend.tsx @@ -0,0 +1,5 @@ +const Block = ( { children }: { children: JSX.Element } ): JSX.Element => { + return
{ children }
; +}; + +export default Block; diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/index.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/index.tsx new file mode 100644 index 00000000000..e2f8cb92acb --- /dev/null +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-items-block/index.tsx @@ -0,0 +1,24 @@ +/** + * External dependencies + */ +import { Icon, grid } from '@woocommerce/icons'; +import { registerFeaturePluginBlockType } from '@woocommerce/block-settings'; + +/** + * Internal dependencies + */ +import { Edit, Save } from './edit'; +import metadata from './block.json'; + +registerFeaturePluginBlockType( metadata, { + icon: { + src: ( + + ), + }, + edit: Edit, + save: Save, +} ); diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.json b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.json index 86899a0bd37..e2b67b81947 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.json +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.json @@ -16,11 +16,11 @@ "type": "object", "default": { "remove": true, - "move": true + "move": false } } }, - "parent": [ "woocommerce/filled-mini-cart-contents-block" ], + "parent": [ "woocommerce/mini-cart-items-block" ], "textdomain": "woo-gutenberg-products-block", "apiVersion": 2 } diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.tsx index 0e44961a2cc..507ac89b888 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/block.tsx @@ -11,7 +11,7 @@ import CartLineItemsTable from '../../../cart/cart-line-items-table'; const Block = (): JSX.Element => { const { cartItems, cartIsLoading } = useStoreCart(); return ( -
+
{
); }; + +export const Save = (): JSX.Element => { + return
; +}; diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/index.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/index.tsx index db819ee6a85..8bbea4ba98d 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/index.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-products-table-block/index.tsx @@ -7,7 +7,7 @@ import { registerFeaturePluginBlockType } from '@woocommerce/block-settings'; /** * Internal dependencies */ -import { Edit } from './edit'; +import { Edit, Save } from './edit'; import metadata from './block.json'; registerFeaturePluginBlockType( metadata, { @@ -18,4 +18,5 @@ registerFeaturePluginBlockType( metadata, { /> ), edit: Edit, + save: Save, } ); diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/edit.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/edit.tsx index a71c28f651a..c3aae20279e 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/edit.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/edit.tsx @@ -17,3 +17,7 @@ export const Edit = (): JSX.Element => {
); }; + +export const Save = (): JSX.Element => { + return
; +}; diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/index.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/index.tsx index 10c30ecce84..ffe3ee8006f 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/index.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/mini-cart-title-block/index.tsx @@ -7,7 +7,7 @@ import { registerFeaturePluginBlockType } from '@woocommerce/block-settings'; /** * Internal dependencies */ -import { Edit } from './edit'; +import { Edit, Save } from './edit'; import metadata from './block.json'; registerFeaturePluginBlockType( metadata, { @@ -20,4 +20,5 @@ registerFeaturePluginBlockType( metadata, { ), }, edit: Edit, + save: Save, } ); diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/register-components.ts b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/register-components.ts index dc263093954..a9f4eebe188 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/register-components.ts +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/register-components.ts @@ -12,6 +12,7 @@ import filledMiniCartMetadata from './filled-mini-cart-contents-block/block.json import miniCartTitleMetadata from './mini-cart-title-block/block.json'; import miniCartProductsTableMetadata from './mini-cart-products-table-block/block.json'; import miniCartFooterMetadata from './mini-cart-footer-block/block.json'; +import miniCartItemsMetadata from './mini-cart-items-block/block.json'; // Modify webpack publicPath at runtime based on location of WordPress Plugin. // eslint-disable-next-line no-undef,camelcase @@ -44,6 +45,15 @@ registerCheckoutBlock( { ), } ); +registerCheckoutBlock( { + metadata: miniCartItemsMetadata, + component: lazy( () => + import( + /* webpackChunkName: "mini-cart-contents-block/items" */ './mini-cart-items-block/frontend' + ) + ), +} ); + registerCheckoutBlock( { metadata: miniCartProductsTableMetadata, component: lazy( () => diff --git a/assets/js/blocks/cart-checkout/mini-cart/style.scss b/assets/js/blocks/cart-checkout/mini-cart/style.scss index 65fce374d96..4f2512807f8 100644 --- a/assets/js/blocks/cart-checkout/mini-cart/style.scss +++ b/assets/js/blocks/cart-checkout/mini-cart/style.scss @@ -65,9 +65,18 @@ } .wc-block-mini-cart__items { + display: flex; + flex-direction: column; flex-grow: 1; - overflow-y: auto; + overflow-y: hidden; padding: 0 $gap; + } + + .wc-block-mini-cart__products-table { + margin-bottom: auto; + margin-right: -$gap; + overflow-y: auto; + padding-right: $gap; .wc-block-cart-items__row:last-child::after { content: none; @@ -86,8 +95,8 @@ justify-content: center; } -.wc-block-mini-cart__title { - @include font-size(large); +h2.wc-block-mini-cart__title { + @include font-size(larger); margin: $gap-largest $gap 0; } diff --git a/packages/checkout/blocks-registry/types.ts b/packages/checkout/blocks-registry/types.ts index b172f032ea4..7dbcea192f7 100644 --- a/packages/checkout/blocks-registry/types.ts +++ b/packages/checkout/blocks-registry/types.ts @@ -21,6 +21,7 @@ export enum innerBlockAreas { MINI_CART = 'woocommerce/mini-cart-contents', EMPTY_MINI_CART = 'woocommerce/empty-mini-cart-contents-block', FILLED_MINI_CART = 'woocommerce/filled-mini-cart-contents-block', + MINI_CART_ITEMS = 'woocommerce/mini-cart-items-block', } interface CheckoutBlockOptionsMetadata extends Partial< BlockConfiguration > { diff --git a/templates/parts/mini-cart.html b/templates/parts/mini-cart.html index c2400ba76ed..bd2838e4459 100644 --- a/templates/parts/mini-cart.html +++ b/templates/parts/mini-cart.html @@ -2,9 +2,22 @@
- - - + +
+
+ + +
+ +
+
+ +
+ + + +
From 7b9942410e94d31e5c54e4ab0f4dd45d64043306 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Sat, 8 Jan 2022 08:19:01 +0700 Subject: [PATCH 17/31] Mini Cart Contents block improvements (#5446) --- .../empty-mini-cart-contents-block/edit.tsx | 59 +++++++++++-------- .../shared/use-view-switcher.tsx | 32 +++++++--- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/empty-mini-cart-contents-block/edit.tsx b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/empty-mini-cart-contents-block/edit.tsx index a976a4b0235..da00b4387d6 100644 --- a/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/empty-mini-cart-contents-block/edit.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart-contents/inner-blocks/empty-mini-cart-contents-block/edit.tsx @@ -1,40 +1,50 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; import { useBlockProps, InnerBlocks } from '@wordpress/block-editor'; -import { innerBlockAreas } from '@woocommerce/blocks-checkout'; -import type { TemplateArray } from '@wordpress/blocks'; import { useEditorContext } from '@woocommerce/base-context'; +import { getBlockTypes } from '@wordpress/blocks'; /** * Internal dependencies */ -import { useForcedLayout, getAllowedBlocks } from '../../../shared'; -export const Edit = ( { clientId }: { clientId: string } ): JSX.Element => { +const EXCLUDED_BLOCKS: readonly string[] = [ + 'woocommerce/mini-cart', + 'woocommerce/single-product', + 'core/post-template', + 'core/comment-template', + 'core/query-pagination', + 'core/comments-query-loop', + 'core/post-comments-form', + 'core/post-comments-link', + 'core/post-comments-count', + 'core/comments-pagination', + 'core/post-navigation-link', +]; + +export const Edit = (): JSX.Element => { const blockProps = useBlockProps(); - const allowedBlocks = getAllowedBlocks( innerBlockAreas.EMPTY_MINI_CART ); const { currentView } = useEditorContext(); + const allowedBlocks = getBlockTypes() + .filter( ( block ) => { + if ( EXCLUDED_BLOCKS.includes( block.name ) ) { + return false; + } - const defaultTemplate = ( [ - [ - 'core/heading', - { - content: __( - 'Empty mini cart content', - 'woo-gutenberg-products-block' - ), - level: 2, - }, - ], - ].filter( Boolean ) as unknown ) as TemplateArray; + // Exclude child blocks of EXCLUDED_BLOCKS. + if ( + block.parent && + block.parent.filter( ( value ) => + EXCLUDED_BLOCKS.includes( value ) + ).length > 0 + ) { + return false; + } - useForcedLayout( { - clientId, - registeredBlocks: allowedBlocks, - defaultTemplate, - } ); + return true; + } ) + .map( ( { name } ) => name ); return (
{ } >
diff --git a/assets/js/blocks/cart-checkout/shared/use-view-switcher.tsx b/assets/js/blocks/cart-checkout/shared/use-view-switcher.tsx index d9a92290c96..dc25faf363e 100644 --- a/assets/js/blocks/cart-checkout/shared/use-view-switcher.tsx +++ b/assets/js/blocks/cart-checkout/shared/use-view-switcher.tsx @@ -14,6 +14,10 @@ interface View { icon: string | JSX.Element; } +function getView( viewName: string, views: View[] ) { + return views.find( ( view ) => view.view === viewName ); +} + export const useViewSwitcher = ( clientId: string, views: View[] @@ -32,7 +36,25 @@ export const useViewSwitcher = ( const selectedBlockClientId = getSelectedBlockClientId(); useEffect( () => { + const selectedBlock = getBlock( selectedBlockClientId ); + + if ( ! selectedBlock ) { + return; + } + + if ( currentView.view === selectedBlock.name ) { + return; + } + const viewNames = views.map( ( { view } ) => view ); + + if ( viewNames.includes( selectedBlock.name ) ) { + const newView = getView( selectedBlock.name, views ); + if ( newView ) { + return setCurrentView( newView ); + } + } + const parentBlockIds = getBlockParentsByBlockName( selectedBlockClientId, viewNames @@ -47,15 +69,11 @@ export const useViewSwitcher = ( return; } - const filteredViews = views.filter( - ( { view } ) => view === parentBlock.name - ); + const newView = getView( parentBlock.name, views ); - if ( filteredViews.length !== 1 ) { - return; + if ( newView ) { + setCurrentView( newView ); } - - setCurrentView( filteredViews[ 0 ] ); }, [ getBlockParentsByBlockName, selectedBlockClientId, From 2cb9a483762cd0d642ac5583ccaa4184baf25374 Mon Sep 17 00:00:00 2001 From: jonny-bull <53259768+jonny-bull@users.noreply.github.com> Date: Mon, 10 Jan 2022 13:39:24 +0000 Subject: [PATCH 18/31] Add stock level filter to product blocks (#4943) --- assets/js/blocks/product-new/block.js | 14 +++ assets/js/blocks/product-on-sale/block.js | 14 +++ assets/js/blocks/product-tag/block.js | 14 +++ assets/js/blocks/product-tag/index.js | 8 ++ assets/js/blocks/product-top-rated/block.js | 14 +++ .../js/blocks/products-by-attribute/block.js | 14 +++ .../js/blocks/products-by-attribute/index.js | 8 ++ assets/js/blocks/products/attributes.js | 9 ++ assets/js/blocks/products/edit.js | 106 ++++++++++-------- .../product-stock-control/index.tsx | 85 ++++++++++++++ assets/js/utils/shared-attributes.js | 8 ++ src/BlockTypes/AbstractProductGrid.php | 40 ++++++- src/BlockTypes/ProductTag.php | 1 + src/BlockTypes/ProductsByAttribute.php | 1 + 14 files changed, 291 insertions(+), 45 deletions(-) create mode 100644 assets/js/editor-components/product-stock-control/index.tsx diff --git a/assets/js/blocks/product-new/block.js b/assets/js/blocks/product-new/block.js index 6cb4b7dc8f9..1c26d0ac944 100644 --- a/assets/js/blocks/product-new/block.js +++ b/assets/js/blocks/product-new/block.js @@ -10,6 +10,7 @@ import PropTypes from 'prop-types'; import GridContentControl from '@woocommerce/editor-components/grid-content-control'; import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; import ProductCategoryControl from '@woocommerce/editor-components/product-category-control'; +import ProductStockControl from '@woocommerce/editor-components/product-stock-control'; import { gridBlockPreview } from '@woocommerce/resource-previews'; import { getSetting } from '@woocommerce/settings'; @@ -26,6 +27,7 @@ class ProductNewestBlock extends Component { contentVisibility, rows, alignButtons, + stockStatus, } = attributes; return ( @@ -56,6 +58,18 @@ class ProductNewestBlock extends Component { } /> + + + + + + ); } diff --git a/assets/js/blocks/product-tag/block.js b/assets/js/blocks/product-tag/block.js index 5d82c4879d9..90d858e9b8d 100644 --- a/assets/js/blocks/product-tag/block.js +++ b/assets/js/blocks/product-tag/block.js @@ -18,6 +18,7 @@ import GridContentControl from '@woocommerce/editor-components/grid-content-cont import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; import ProductTagControl from '@woocommerce/editor-components/product-tag-control'; import ProductOrderbyControl from '@woocommerce/editor-components/product-orderby-control'; +import ProductStockControl from '@woocommerce/editor-components/product-stock-control'; import { Icon, more } from '@woocommerce/icons'; import { gridBlockPreview } from '@woocommerce/resource-previews'; import { getSetting } from '@woocommerce/settings'; @@ -91,6 +92,7 @@ class ProductsByTagBlock extends Component { orderby, rows, alignButtons, + stockStatus, } = attributes; return ( @@ -150,6 +152,18 @@ class ProductsByTagBlock extends Component { value={ orderby } /> + + + ); } diff --git a/assets/js/blocks/product-tag/index.js b/assets/js/blocks/product-tag/index.js index 87056fbc579..ba1f606b14a 100644 --- a/assets/js/blocks/product-tag/index.js +++ b/assets/js/blocks/product-tag/index.js @@ -109,6 +109,14 @@ registerBlockType( 'woocommerce/product-tag', { type: 'boolean', default: false, }, + + /** + * Whether to display in stock, out of stock or backorder products. + */ + stockStatus: { + type: 'array', + default: getSetting( 'stockStatusOptions', [] ), + }, }, /** diff --git a/assets/js/blocks/product-top-rated/block.js b/assets/js/blocks/product-top-rated/block.js index 795ea3e384f..98968ab9e70 100644 --- a/assets/js/blocks/product-top-rated/block.js +++ b/assets/js/blocks/product-top-rated/block.js @@ -10,6 +10,7 @@ import PropTypes from 'prop-types'; import GridContentControl from '@woocommerce/editor-components/grid-content-control'; import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; import ProductCategoryControl from '@woocommerce/editor-components/product-category-control'; +import ProductStockControl from '@woocommerce/editor-components/product-stock-control'; import { gridBlockPreview } from '@woocommerce/resource-previews'; import { getSetting } from '@woocommerce/settings'; @@ -26,6 +27,7 @@ class ProductTopRatedBlock extends Component { contentVisibility, rows, alignButtons, + stockStatus, } = attributes; return ( @@ -75,6 +77,18 @@ class ProductTopRatedBlock extends Component { } /> + + + ); } diff --git a/assets/js/blocks/products-by-attribute/block.js b/assets/js/blocks/products-by-attribute/block.js index 62a7b472e4e..e7cd6d82f91 100644 --- a/assets/js/blocks/products-by-attribute/block.js +++ b/assets/js/blocks/products-by-attribute/block.js @@ -19,6 +19,7 @@ import GridContentControl from '@woocommerce/editor-components/grid-content-cont import GridLayoutControl from '@woocommerce/editor-components/grid-layout-control'; import ProductAttributeTermControl from '@woocommerce/editor-components/product-attribute-term-control'; import ProductOrderbyControl from '@woocommerce/editor-components/product-orderby-control'; +import ProductStockControl from '@woocommerce/editor-components/product-stock-control'; import { gridBlockPreview } from '@woocommerce/resource-previews'; import { getSetting } from '@woocommerce/settings'; @@ -36,6 +37,7 @@ class ProductsByAttributeBlock extends Component { orderby, rows, alignButtons, + stockStatus, } = this.props.attributes; return ( @@ -100,6 +102,18 @@ class ProductsByAttributeBlock extends Component { value={ orderby } /> + + + ); } diff --git a/assets/js/blocks/products-by-attribute/index.js b/assets/js/blocks/products-by-attribute/index.js index 6f9b7c798aa..abaed9c437c 100644 --- a/assets/js/blocks/products-by-attribute/index.js +++ b/assets/js/blocks/products-by-attribute/index.js @@ -116,6 +116,14 @@ registerBlockType( blockTypeName, { type: 'boolean', default: false, }, + + /** + * Whether to display in stock, out of stock or backorder products. + */ + stockStatus: { + type: 'string', + default: getSetting( 'stockStatusOptions', [] ), + }, }, /** diff --git a/assets/js/blocks/products/attributes.js b/assets/js/blocks/products/attributes.js index c83ea501d70..37bfb5a86d8 100644 --- a/assets/js/blocks/products/attributes.js +++ b/assets/js/blocks/products/attributes.js @@ -18,6 +18,7 @@ export const defaults = { orderby: 'date', layoutConfig: DEFAULT_PRODUCT_LIST_LAYOUT, isPreview: false, + stockStatus: 'any', }; export const attributes = { @@ -64,4 +65,12 @@ export const attributes = { type: 'boolean', default: false, }, + + /** + * Whether to display in stock, out of stock or backorder products. + */ + stockStatus: { + type: 'string', + default: 'any', + }, }; diff --git a/assets/js/blocks/products/edit.js b/assets/js/blocks/products/edit.js index 1d7d57dd493..34591a9d3f4 100644 --- a/assets/js/blocks/products/edit.js +++ b/assets/js/blocks/products/edit.js @@ -2,7 +2,8 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { ToggleControl, SelectControl } from '@wordpress/components'; +import { PanelBody, ToggleControl, SelectControl } from '@wordpress/components'; +import ProductStockControl from '@woocommerce/editor-components/product-stock-control'; export const getSharedContentControls = ( attributes, setAttributes ) => { const { contentVisibility } = attributes; @@ -27,48 +28,65 @@ export const getSharedContentControls = ( attributes, setAttributes ) => { export const getSharedListControls = ( attributes, setAttributes ) => { return ( - setAttributes( { orderby } ) } - /> +
+ setAttributes( { orderby } ) } + /> + + + +
); }; diff --git a/assets/js/editor-components/product-stock-control/index.tsx b/assets/js/editor-components/product-stock-control/index.tsx new file mode 100644 index 00000000000..553f27c2b70 --- /dev/null +++ b/assets/js/editor-components/product-stock-control/index.tsx @@ -0,0 +1,85 @@ +/** + * External dependencies + */ +import CheckboxList from '@woocommerce/base-components/checkbox-list'; +import { getSetting } from '@woocommerce/settings'; +import { useCallback, useState } from '@wordpress/element'; + +export interface ProductStockControlProps { + value: Array< string >; + setAttributes: ( attributes: Record< string, unknown > ) => void; +} + +/** + * A pre-configured SelectControl for product stock settings. + */ +const ProductStockControl = ( { + value, + setAttributes, +}: ProductStockControlProps ): JSX.Element => { + // Should out of stock items be hidden? + const [ hideOutOfStockItems ] = useState( + getSetting( 'hideOutOfStockItems', false ) + ); + + // Get the stock status options. + const [ { outofstock, ...otherStockStatusOptions } ] = useState( + getSetting( 'stockStatusOptions', {} ) + ); + + // Determine whether or not to use the out of stock status. + const [ STOCK_STATUS_OPTIONS ] = useState( + hideOutOfStockItems + ? otherStockStatusOptions + : { outofstock, ...otherStockStatusOptions } + ); + + // Set the initial state to the default or saved value. + const [ checkedOptions, setChecked ] = useState( value ); + + /** + * Valid options must be in an array of [ 'value' : 'mystatus', 'label' : 'My label' ] format. + * stockStatusOptions are returned as [ 'mystatus' : 'My label' ]. + * Formatting is corrected here. + */ + const [ displayOptions ] = useState( + Object.entries( STOCK_STATUS_OPTIONS ) + .map( ( [ slug, name ] ) => ( { value: slug, label: name } ) ) + .filter( ( status ) => !! status.label ) + .sort( ( a, b ) => a.value.localeCompare( b.value ) ) + ); + + /** + * When a checkbox in the list changes, update state. + */ + const onChange = useCallback( + ( checkedValue: string ) => { + const previouslyChecked = checkedOptions.includes( checkedValue ); + + const newChecked = checkedOptions.filter( + ( filteredValue ) => filteredValue !== checkedValue + ); + + if ( ! previouslyChecked ) { + newChecked.push( checkedValue ); + newChecked.sort(); + } + + setChecked( newChecked ); + setAttributes( { + stockStatus: newChecked, + } ); + }, + [ checkedOptions, setAttributes ] + ); + + return ( + + ); +}; + +export default ProductStockControl; diff --git a/assets/js/utils/shared-attributes.js b/assets/js/utils/shared-attributes.js index 5533b7022ea..ef4a77f9d93 100644 --- a/assets/js/utils/shared-attributes.js +++ b/assets/js/utils/shared-attributes.js @@ -72,4 +72,12 @@ export default { type: 'boolean', default: false, }, + + /** + * Whether to display in stock, out of stock or backorder products. + */ + stockStatus: { + type: 'array', + default: Object.keys( getSetting( 'stockStatusOptions', [] ) ), + }, }; diff --git a/src/BlockTypes/AbstractProductGrid.php b/src/BlockTypes/AbstractProductGrid.php index 25bb57c4b02..b16f97702fc 100644 --- a/src/BlockTypes/AbstractProductGrid.php +++ b/src/BlockTypes/AbstractProductGrid.php @@ -31,6 +31,13 @@ abstract class AbstractProductGrid extends AbstractDynamicBlock { */ protected $query_args = array(); + /** + * Meta query args. + * + * @var array + */ + protected $meta_query = array(); + /** * Get a set of attributes shared across most of the grid blocks. * @@ -50,6 +57,7 @@ protected function get_block_type_attributes() { 'align' => $this->get_schema_align(), 'alignButtons' => $this->get_schema_boolean( false ), 'isPreview' => $this->get_schema_boolean( false ), + 'stockStatus' => array_keys( wc_get_product_stock_status_options() ), ); } @@ -161,6 +169,7 @@ protected function parse_attributes( $attributes ) { 'rating' => true, 'button' => true, ), + 'stockStatus' => array_keys( wc_get_product_stock_status_options() ), ); return wp_parse_args( $attributes, $defaults ); @@ -172,6 +181,9 @@ protected function parse_attributes( $attributes ) { * @return array */ protected function parse_query_args() { + // Store the original meta query. + $this->meta_query = WC()->query->get_meta_query(); + $query_args = array( 'post_type' => 'product', 'post_status' => 'publish', @@ -180,7 +192,7 @@ protected function parse_query_args() { 'no_found_rows' => false, 'orderby' => '', 'order' => '', - 'meta_query' => WC()->query->get_meta_query(), // phpcs:ignore WordPress.DB.SlowDBQuery + 'meta_query' => $this->meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery 'tax_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery 'posts_per_page' => $this->get_products_limit(), ); @@ -189,6 +201,7 @@ protected function parse_query_args() { $this->set_ordering_query_args( $query_args ); $this->set_categories_query_args( $query_args ); $this->set_visibility_query_args( $query_args ); + $this->set_stock_status_query_args( $query_args ); return $query_args; } @@ -272,6 +285,29 @@ protected function set_visibility_query_args( &$query_args ) { ); } + /** + * Set which stock status to use when displaying products. + * + * @param array $query_args Query args. + * @return void + */ + protected function set_stock_status_query_args( &$query_args ) { + // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query + if ( isset( $this->attributes['stockStatus'] ) && + ( array_keys( wc_get_product_stock_status_options() ) !== $this->attributes['stockStatus'] || [] !== $this->attributes['stockStatus'] ) + ) { + // Reset meta_query then update with our stock status. + $query_args['meta_query'] = $this->meta_query; + $query_args['meta_query'][] = array( + 'key' => '_stock_status', + 'value' => $this->attributes['stockStatus'], + ); + } else { + $query_args['meta_query'] = $this->meta_query; + } + // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query + } + /** * Works out the item limit based on rows and columns, or returns default. * @@ -502,6 +538,7 @@ protected function get_title_html( $product ) { if ( empty( $this->attributes['contentVisibility']['title'] ) ) { return ''; } + return '
' . wp_kses_post( $product->get_title() ) . '
'; } @@ -625,5 +662,6 @@ protected function enqueue_data( array $attributes = [] ) { $this->asset_data_registry->add( 'min_rows', wc_get_theme_support( 'product_blocks::min_rows', 1 ), true ); $this->asset_data_registry->add( 'max_rows', wc_get_theme_support( 'product_blocks::max_rows', 6 ), true ); $this->asset_data_registry->add( 'default_rows', wc_get_theme_support( 'product_blocks::default_rows', 3 ), true ); + $this->asset_data_registry->add( 'stock_status_options', wc_get_product_stock_status_options(), true ); } } diff --git a/src/BlockTypes/ProductTag.php b/src/BlockTypes/ProductTag.php index f4be1af9545..9bb64ac43f5 100644 --- a/src/BlockTypes/ProductTag.php +++ b/src/BlockTypes/ProductTag.php @@ -48,6 +48,7 @@ protected function get_block_type_attributes() { 'default' => 'any', ), 'isPreview' => $this->get_schema_boolean( false ), + 'stockStatus' => array_keys( wc_get_product_stock_status_options() ), ); } diff --git a/src/BlockTypes/ProductsByAttribute.php b/src/BlockTypes/ProductsByAttribute.php index 9d559b89cc2..8bbf6363f50 100644 --- a/src/BlockTypes/ProductsByAttribute.php +++ b/src/BlockTypes/ProductsByAttribute.php @@ -67,6 +67,7 @@ protected function get_block_type_attributes() { 'orderby' => $this->get_schema_orderby(), 'rows' => $this->get_schema_number( wc_get_theme_support( 'product_blocks::default_rows', 3 ) ), 'isPreview' => $this->get_schema_boolean( false ), + 'stockStatus' => array_keys( wc_get_product_stock_status_options() ), ); } } From c383e4040d1ffa29efb5ea1bd0c84026cd431a70 Mon Sep 17 00:00:00 2001 From: Thomas Roberts <5656702+opr@users.noreply.github.com> Date: Tue, 11 Jan 2022 10:27:37 +0000 Subject: [PATCH 19/31] Add margin below shipping packages in checkout (#5529) * Add margin below shipping packages in checkout * Add margin below shipping packages in checkout but not in cart --- .../shipping-rates-control-package/style.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss b/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss index d8e590c2912..d3495745880 100644 --- a/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss +++ b/assets/js/base/components/cart-checkout/shipping-rates-control-package/style.scss @@ -1,4 +1,5 @@ .wc-block-components-shipping-rates-control__package { + .wc-block-components-panel__button { margin-bottom: 0; margin-top: 0; @@ -41,3 +42,13 @@ content: ", "; white-space: pre; } + +// Target the shipping selection in checkout only, the Cart block has enough spacing because of the buttons on the panel. +.wc-block-checkout .wc-block-components-shipping-rates-control__package { + margin-bottom: em($gap-large); + + &:last-of-type { + margin-bottom: 0; + } + +} From 5a81462120f5962a9ffff6e7c428f7dfa3b67697 Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Tue, 11 Jan 2022 11:28:26 +0100 Subject: [PATCH 20/31] Update release-initial-checklist.md (#5537) Add information about the the confirmation email step for the release --- .github/release-initial-checklist.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/release-initial-checklist.md b/.github/release-initial-checklist.md index bfae845eb2a..f12b776641a 100644 --- a/.github/release-initial-checklist.md +++ b/.github/release-initial-checklist.md @@ -56,6 +56,7 @@ Additionally, make sure to differentiate between things in the testing notes tha * Note: the script automatically updates version numbers on Github (commits on your behalf). * **ALERT**: This script will ask you if this release will be deployed to WordPress.org. You should answer yes for this release even if it is a pre-release. * A GitHub release will automatically be created and this will trigger a workflow that automatically deploys the plugin to WordPress.org. + * An email confirmation is required before the new version will be released, so check your email in order to confirm the release. * [ ] Edit the [GitHub release](https://github.com/woocommerce/woocommerce-gutenberg-products-block/releases) and copy changelog into the release notes. Ensure there is a release with the correct version, the one you entered above. * [ ] The `#team-rubik` slack instance will be notified about the progress with the WordPress.org deploy. Watch for that. If anything goes wrong, an error will be reported and you can followup via the GitHub actions tab and the log for that workflow. From 8c7c9009a1a7cd52165d6d1a381c7c0607649f5b Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 11 Jan 2022 10:53:38 +0000 Subject: [PATCH 21/31] Remove Built-in Stripe Integration in favour of Stripe Extension (#5449) * Remove Stripe from PHP side * Remove stripe from client * fix const name in tests * Update docs --- .../test/payment-method-data-context.js | 26 +- .../use-payment-method-registration.ts | 5 +- .../no-payment-methods/index.js | 2 +- .../payment-methods/test/payment-methods.js | 10 +- .../stripe/credit-card/constants.js | 1 - .../stripe/credit-card/elements.js | 161 -------- .../stripe/credit-card/index.js | 65 ---- .../stripe/credit-card/payment-method.js | 92 ----- .../credit-card/use-checkout-subscriptions.js | 97 ----- .../stripe/credit-card/use-element-options.js | 115 ------ .../stripe/credit-card/use-payment-intents.js | 104 ----- .../credit-card/use-payment-processing.js | 166 -------- .../payment-methods/stripe/index.js | 22 -- .../payment-request/apple-pay-preview.js | 2 - .../stripe/payment-request/constants.js | 7 - .../stripe/payment-request/index.js | 76 ---- .../payment-request-express.js | 113 ------ .../use-checkout-subscriptions.js | 241 ------------ .../payment-request/use-event-handlers.js | 48 --- .../payment-request/use-initialization.js | 251 ------------ .../stripe/stripe-utils/constants.js | 28 -- .../stripe/stripe-utils/index.js | 3 - .../stripe/stripe-utils/load-stripe.js | 22 -- .../stripe/stripe-utils/normalize.js | 176 --------- .../stripe/stripe-utils/type-defs.js | 324 ---------------- .../stripe/stripe-utils/utils.js | 281 -------------- assets/js/previews/saved-payment-methods.js | 2 +- assets/js/types/type-defs/payments.ts | 2 +- bin/hook-docs/data/filters.json | 94 ----- bin/webpack-entries.js | 2 - docs/contributors/folder-structure.md | 2 +- docs/extensibility/filters.md | 94 ----- docs/testing/smoke-testing.md | 2 +- package-lock.json | 33 -- package.json | 2 - src/Domain/Bootstrap.php | 11 - src/Payments/Api.php | 7 - src/Payments/Integrations/Stripe.php | 367 ------------------ 38 files changed, 25 insertions(+), 3031 deletions(-) delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/constants.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/elements.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/index.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/payment-method.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-checkout-subscriptions.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-element-options.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-intents.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-processing.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/index.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/payment-request/apple-pay-preview.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/payment-request/constants.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/payment-request/payment-request-express.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-checkout-subscriptions.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-event-handlers.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-initialization.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/constants.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/index.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/load-stripe.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/normalize.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/type-defs.js delete mode 100644 assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/utils.js delete mode 100644 src/Payments/Integrations/Stripe.php diff --git a/assets/js/base/context/providers/cart-checkout/payment-methods/test/payment-method-data-context.js b/assets/js/base/context/providers/cart-checkout/payment-methods/test/payment-method-data-context.js index f2e145fb1bb..740459ab9f6 100644 --- a/assets/js/base/context/providers/cart-checkout/payment-methods/test/payment-method-data-context.js +++ b/assets/js/base/context/providers/cart-checkout/payment-methods/test/payment-method-data-context.js @@ -44,7 +44,7 @@ jest.mock( '@woocommerce/settings', () => { cc: [ { method: { - gateway: 'stripe', + gateway: 'credit-card', last4: '4242', brand: 'Visa', }, @@ -75,7 +75,7 @@ const registerMockPaymentMethods = ( savedCards = true ) => { ariaLabel: name, } ); } ); - [ 'stripe' ].forEach( ( name ) => { + [ 'credit-card' ].forEach( ( name ) => { registerPaymentMethod( { name, label: name, @@ -121,7 +121,7 @@ const registerMockPaymentMethods = ( savedCards = true ) => { }; const resetMockPaymentMethods = () => { - [ 'cheque', 'bacs', 'stripe' ].forEach( ( name ) => { + [ 'cheque', 'bacs', 'credit-card' ].forEach( ( name ) => { __experimentalDeRegisterPaymentMethod( name ); } ); [ 'express-payment' ].forEach( ( name ) => { @@ -255,8 +255,8 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned void null } /> { 'Active Payment Method: ' + activePaymentMethod } - { paymentMethodData[ 'wc-stripe-payment-token' ] && ( - Stripe token + { paymentMethodData[ 'wc-credit-card-payment-token' ] && ( + credit-card token ) } ); @@ -276,11 +276,11 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned // Should initialize by default the default saved payment method. await waitFor( () => { const activePaymentMethod = screen.queryByText( - /Active Payment Method: stripe/ + /Active Payment Method: credit-card/ ); - const stripeToken = screen.queryByText( /Stripe token/ ); + const creditCardToken = screen.queryByText( /credit-card token/ ); expect( activePaymentMethod ).not.toBeNull(); - expect( stripeToken ).not.toBeNull(); + expect( creditCardToken ).not.toBeNull(); } ); act( () => { @@ -294,9 +294,9 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned const activePaymentMethod = screen.queryByText( /Active Payment Method: express-payment/ ); - const stripeToken = screen.queryByText( /Stripe token/ ); + const creditCardToken = screen.queryByText( /credit-card token/ ); expect( activePaymentMethod ).not.toBeNull(); - expect( stripeToken ).toBeNull(); + expect( creditCardToken ).toBeNull(); } ); act( () => { @@ -310,11 +310,11 @@ describe( 'Testing Payment Method Data Context Provider with saved cards turned await waitFor( () => { const activePaymentMethod = screen.queryByText( - /Active Payment Method: stripe/ + /Active Payment Method: credit-card/ ); - const stripeToken = screen.queryByText( /Stripe token/ ); + const creditCardToken = screen.queryByText( /credit-card token/ ); expect( activePaymentMethod ).not.toBeNull(); - expect( stripeToken ).not.toBeNull(); + expect( creditCardToken ).not.toBeNull(); } ); } ); } ); diff --git a/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts b/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts index 58c2de1d44e..9a1ac7274ed 100644 --- a/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts +++ b/assets/js/base/context/providers/cart-checkout/payment-methods/use-payment-method-registration.ts @@ -135,7 +135,7 @@ const usePaymentMethodRegistration = ( } catch ( e ) { if ( CURRENT_USER_IS_ADMIN || isEditor ) { const errorText = sprintf( - /* translators: %s the id of the payment method being registered (bank transfer, Stripe...) */ + /* translators: %s the id of the payment method being registered (bank transfer, cheque...) */ __( `There was an error registering the payment method with id '%s': `, 'woo-gutenberg-products-block' @@ -153,8 +153,7 @@ const usePaymentMethodRegistration = ( // Re-dispatch available payment methods to store. dispatcher( availablePaymentMethods ); - // Note: some payment methods use the `canMakePayment` callback to initialize / setup. - // Example: Stripe CC, Stripe Payment Request. + // Note: Some 4rd party payment methods use the `canMakePayment` callback to initialize / setup. // That's why we track "is initialized" state here. setIsInitialized( true ); }, [ diff --git a/assets/js/blocks/cart-checkout/payment-methods/no-payment-methods/index.js b/assets/js/blocks/cart-checkout/payment-methods/no-payment-methods/index.js index 348dc67e3af..ec17ab464d6 100644 --- a/assets/js/blocks/cart-checkout/payment-methods/no-payment-methods/index.js +++ b/assets/js/blocks/cart-checkout/payment-methods/no-payment-methods/index.js @@ -38,7 +38,7 @@ const NoPaymentMethodsPlaceholder = () => { > { __( - 'Your store does not have any payment methods configured that support the checkout block. Once you have configured a compatible payment method (e.g. Stripe) it will be shown here.', + 'Your store does not have any payment methods configured that support the checkout block. Once you have configured a compatible payment method it will be shown here.', 'woo-gutenberg-products-block' ) } diff --git a/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js b/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js index f39a4933f14..a54431ca86f 100644 --- a/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js +++ b/assets/js/blocks/cart-checkout/payment-methods/test/payment-methods.js @@ -35,7 +35,7 @@ jest.mock( () => ( { onChange } ) => ( <> Payment method options - @@ -43,7 +43,7 @@ jest.mock( ); const registerMockPaymentMethods = () => { - [ 'stripe' ].forEach( ( name ) => { + [ 'credit-card' ].forEach( ( name ) => { registerPaymentMethod( { name, label: name, @@ -62,7 +62,7 @@ const registerMockPaymentMethods = () => { }; const resetMockPaymentMethods = () => { - [ 'stripe' ].forEach( ( name ) => { + [ 'credit-card' ].forEach( ( name ) => { __experimentalDeRegisterPaymentMethod( name ); } ); }; @@ -137,7 +137,7 @@ describe( 'PaymentMethods', () => { expect( savedPaymentMethodOptions ).not.toBeNull(); expect( paymentMethodOptions ).not.toBeNull(); const savedToken = screen.queryByText( - /Active Payment Method: stripe/ + /Active Payment Method: credit-card/ ); expect( savedToken ).toBeNull(); } ); @@ -146,7 +146,7 @@ describe( 'PaymentMethods', () => { await waitFor( () => { const activePaymentMethod = screen.queryByText( - /Active Payment Method: stripe/ + /Active Payment Method: credit-card/ ); expect( activePaymentMethod ).not.toBeNull(); } ); diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/constants.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/constants.js deleted file mode 100644 index 24e1e1d470a..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const PAYMENT_METHOD_NAME = 'stripe'; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/elements.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/elements.js deleted file mode 100644 index 434a1459b95..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/elements.js +++ /dev/null @@ -1,161 +0,0 @@ -/** - * External dependencies - */ -import { useState } from '@wordpress/element'; -import { __ } from '@wordpress/i18n'; -import { - CardElement, - CardNumberElement, - CardExpiryElement, - CardCvcElement, -} from '@stripe/react-stripe-js'; - -/** - * Internal dependencies - */ -import { useElementOptions } from './use-element-options'; - -/** @typedef {import('react')} React */ - -const baseTextInputStyles = 'wc-block-gateway-input'; - -/** - * InlineCard component - * - * @param {Object} props Incoming props for the component. - * @param {React.ReactElement} props.inputErrorComponent - * @param {function(any):any} props.onChange - */ -export const InlineCard = ( { - inputErrorComponent: ValidationInputError, - onChange, -} ) => { - const [ isEmpty, setIsEmpty ] = useState( true ); - const { options, onActive, error, setError } = useElementOptions( { - hidePostalCode: true, - } ); - const errorCallback = ( event ) => { - if ( event.error ) { - setError( event.error.message ); - } else { - setError( '' ); - } - setIsEmpty( event.empty ); - onChange( event ); - }; - return ( - <> -
- onActive( isEmpty ) } - onFocus={ () => onActive( isEmpty ) } - onChange={ errorCallback } - /> - -
- - - ); -}; - -/** - * CardElements component. - * - * @param {Object} props - * @param {function(any):any} props.onChange - * @param {React.ReactElement} props.inputErrorComponent - */ -export const CardElements = ( { - onChange, - inputErrorComponent: ValidationInputError, -} ) => { - const [ isEmpty, setIsEmpty ] = useState( { - cardNumber: true, - cardExpiry: true, - cardCvc: true, - } ); - const { - options: cardNumOptions, - onActive: cardNumOnActive, - error: cardNumError, - setError: cardNumSetError, - } = useElementOptions( { showIcon: false } ); - const { - options: cardExpiryOptions, - onActive: cardExpiryOnActive, - error: cardExpiryError, - setError: cardExpirySetError, - } = useElementOptions(); - const { - options: cardCvcOptions, - onActive: cardCvcOnActive, - error: cardCvcError, - setError: cardCvcSetError, - } = useElementOptions(); - const errorCallback = ( errorSetter, elementId ) => ( event ) => { - if ( event.error ) { - errorSetter( event.error.message ); - } else { - errorSetter( '' ); - } - setIsEmpty( { ...isEmpty, [ elementId ]: event.empty } ); - onChange( event ); - }; - return ( -
-
- cardNumOnActive( isEmpty.cardNumber ) } - onBlur={ () => cardNumOnActive( isEmpty.cardNumber ) } - /> - - -
-
- cardExpiryOnActive( isEmpty.cardExpiry ) } - onBlur={ () => cardExpiryOnActive( isEmpty.cardExpiry ) } - id="wc-stripe-card-expiry-element" - /> - - -
-
- cardCvcOnActive( isEmpty.cardCvc ) } - onBlur={ () => cardCvcOnActive( isEmpty.cardCvc ) } - id="wc-stripe-card-code-element" - /> - - -
-
- ); -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/index.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/index.js deleted file mode 100644 index 5ca9889fd42..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/index.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * External dependencies - */ -import { __ } from '@wordpress/i18n'; -import { useEffect, useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { getStripeServerData, loadStripe } from '../stripe-utils'; -import { StripeCreditCard, getStripeCreditCardIcons } from './payment-method'; -import { PAYMENT_METHOD_NAME } from './constants'; - -const stripePromise = loadStripe(); - -const StripeComponent = ( props ) => { - const [ errorMessage, setErrorMessage ] = useState( '' ); - - useEffect( () => { - Promise.resolve( stripePromise ).then( ( { error } ) => { - if ( error ) { - setErrorMessage( error.message ); - } - } ); - }, [ setErrorMessage ] ); - - useEffect( () => { - if ( errorMessage ) { - throw new Error( errorMessage ); - } - }, [ errorMessage ] ); - - return ; -}; - -const StripeLabel = ( props ) => { - const { PaymentMethodLabel } = props.components; - - const labelText = getStripeServerData().title - ? getStripeServerData().title - : __( 'Credit / Debit Card', 'woo-gutenberg-products-block' ); - - return ; -}; - -const cardIcons = getStripeCreditCardIcons(); -const stripeCcPaymentMethod = { - name: PAYMENT_METHOD_NAME, - label: , - content: , - edit: , - icons: cardIcons, - canMakePayment: () => stripePromise, - ariaLabel: __( - 'Stripe Credit Card payment method', - 'woo-gutenberg-products-block' - ), - supports: { - showSavedCards: getStripeServerData().showSavedCards, - showSaveOption: getStripeServerData().showSaveOption, - features: getStripeServerData()?.supports ?? [], - }, -}; - -export default stripeCcPaymentMethod; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/payment-method.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/payment-method.js deleted file mode 100644 index f0b80c5a7f9..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/payment-method.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * External dependencies - */ -import { Elements, useStripe } from '@stripe/react-stripe-js'; -import { useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { getStripeServerData } from '../stripe-utils'; -import { useCheckoutSubscriptions } from './use-checkout-subscriptions'; -import { InlineCard, CardElements } from './elements'; - -/** - * @typedef {import('../stripe-utils/type-defs').Stripe} Stripe - * @typedef {import('../stripe-utils/type-defs').StripePaymentRequest} StripePaymentRequest - * @typedef {import('@woocommerce/type-defs/payment-method-interface').PaymentMethodInterface} RegisteredPaymentMethodProps - */ - -export const getStripeCreditCardIcons = () => { - return Object.entries( getStripeServerData().icons ).map( - ( [ id, { src, alt } ] ) => { - return { - id, - src, - alt, - }; - } - ); -}; - -/** - * Stripe Credit Card component - * - * @param {RegisteredPaymentMethodProps} props Incoming props - */ -const CreditCardComponent = ( { - billing, - eventRegistration, - emitResponse, - components, -} ) => { - const { ValidationInputError, PaymentMethodIcons } = components; - const [ sourceId, setSourceId ] = useState( '' ); - const stripe = useStripe(); - const onStripeError = useCheckoutSubscriptions( - eventRegistration, - billing, - sourceId, - setSourceId, - emitResponse, - stripe - ); - const onChange = ( paymentEvent ) => { - if ( paymentEvent.error ) { - onStripeError( paymentEvent ); - } - setSourceId( '0' ); - }; - const cardIcons = getStripeCreditCardIcons(); - - const renderedCardElement = getStripeServerData().inline_cc_form ? ( - - ) : ( - - ); - return ( - <> - { renderedCardElement } - { PaymentMethodIcons && cardIcons.length && ( - - ) } - - ); -}; - -export const StripeCreditCard = ( props ) => { - const { locale } = getStripeServerData().button; - const { stripe } = props; - - return ( - - - - ); -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-checkout-subscriptions.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-checkout-subscriptions.js deleted file mode 100644 index 6b5555a2551..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-checkout-subscriptions.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * External dependencies - */ -import { useEffect, useCallback, useState } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { getErrorMessageForTypeAndCode } from '../stripe-utils'; -import { usePaymentIntents } from './use-payment-intents'; -import { usePaymentProcessing } from './use-payment-processing'; - -/** - * @typedef {import('@woocommerce/type-defs/payment-method-interface').EventRegistrationProps} EventRegistrationProps - * @typedef {import('@woocommerce/type-defs/payment-method-interface').BillingDataProps} BillingDataProps - * @typedef {import('@woocommerce/type-defs/payment-method-interface').EmitResponseProps} EmitResponseProps - * @typedef {import('../stripe-utils/type-defs').Stripe} Stripe - * @typedef {import('react').Dispatch} SourceIdDispatch - */ - -/** - * A custom hook for the Stripe processing and event observer logic. - * - * @param {EventRegistrationProps} eventRegistration Event registration functions. - * @param {BillingDataProps} billing Various billing data items. - * @param {string} sourceId Current set stripe source id. - * @param {SourceIdDispatch} setSourceId Setter for stripe source id. - * @param {EmitResponseProps} emitResponse Various helpers for usage with observer - * response objects. - * @param {Stripe} stripe The stripe.js object. - * - * @return {function(Object):Object} Returns a function for handling stripe error. - */ -export const useCheckoutSubscriptions = ( - eventRegistration, - billing, - sourceId, - setSourceId, - emitResponse, - stripe -) => { - const [ error, setError ] = useState( '' ); - const onStripeError = useCallback( ( event ) => { - const type = event.error.type; - const code = event.error.code || ''; - const message = - getErrorMessageForTypeAndCode( type, code ) ?? event.error.message; - setError( message ); - return message; - }, [] ); - const { - onCheckoutAfterProcessingWithSuccess, - onPaymentProcessing, - onCheckoutAfterProcessingWithError, - } = eventRegistration; - usePaymentIntents( - stripe, - onCheckoutAfterProcessingWithSuccess, - setSourceId, - emitResponse - ); - usePaymentProcessing( - onStripeError, - error, - stripe, - billing, - emitResponse, - sourceId, - setSourceId, - onPaymentProcessing - ); - // hook into and register callbacks for events. - useEffect( () => { - const onError = ( { processingResponse } ) => { - if ( processingResponse?.paymentDetails?.errorMessage ) { - return { - type: emitResponse.responseTypes.ERROR, - message: processingResponse.paymentDetails.errorMessage, - messageContext: emitResponse.noticeContexts.PAYMENTS, - }; - } - // so we don't break the observers. - return true; - }; - const unsubscribeAfterProcessing = onCheckoutAfterProcessingWithError( - onError - ); - return () => { - unsubscribeAfterProcessing(); - }; - }, [ - onCheckoutAfterProcessingWithError, - emitResponse.noticeContexts.PAYMENTS, - emitResponse.responseTypes.ERROR, - ] ); - return onStripeError; -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-element-options.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-element-options.js deleted file mode 100644 index 17243c9a806..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-element-options.js +++ /dev/null @@ -1,115 +0,0 @@ -/** - * External dependencies - */ -import { useState, useEffect, useCallback } from '@wordpress/element'; - -/** - * @typedef {import('../stripe-utils/type-defs').StripeElementOptions} StripeElementOptions - */ - -/** - * Returns the value of a specific CSS property for the element matched by the provided selector. - * - * @param {string} selector CSS selector that matches the element to query. - * @param {string} property Name of the property to retrieve the style - * value from. - * @param {string} defaultValue Fallback value if the value for the property - * could not be retrieved. - * - * @return {string} The style value of that property in the document element. - */ -const getComputedStyle = ( selector, property, defaultValue ) => { - let elementStyle = {}; - - if ( - typeof document === 'object' && - typeof document.querySelector === 'function' && - typeof window.getComputedStyle === 'function' - ) { - const element = document.querySelector( selector ); - if ( element ) { - elementStyle = window.getComputedStyle( element ); - } - } - - return elementStyle[ property ] || defaultValue; -}; - -/** - * Default options for the stripe elements. - */ -const elementOptions = { - style: { - base: { - iconColor: '#666EE8', - color: '#31325F', - fontSize: getComputedStyle( - '.wc-block-checkout', - 'fontSize', - '16px' - ), - lineHeight: 1.375, // With a font-size of 16px, line-height will be 22px. - '::placeholder': { - color: '#fff', - }, - }, - }, - classes: { - focus: 'focused', - empty: 'empty', - invalid: 'has-error', - }, -}; - -/** - * A custom hook handling options implemented on the stripe elements. - * - * @param {Object} [overloadedOptions] An array of extra options to merge with - * the options provided for the element. - * - * @return {StripeElementOptions} The stripe element options interface - */ -export const useElementOptions = ( overloadedOptions ) => { - const [ isActive, setIsActive ] = useState( false ); - const [ options, setOptions ] = useState( { - ...elementOptions, - ...overloadedOptions, - } ); - const [ error, setError ] = useState( '' ); - - useEffect( () => { - const color = isActive ? '#CFD7E0' : '#fff'; - - setOptions( ( prevOptions ) => { - const showIcon = - typeof prevOptions.showIcon !== 'undefined' - ? { showIcon: isActive } - : {}; - return { - ...prevOptions, - style: { - ...prevOptions.style, - base: { - ...prevOptions.style.base, - '::placeholder': { - color, - }, - }, - }, - ...showIcon, - }; - } ); - }, [ isActive ] ); - - const onActive = useCallback( - ( isEmpty ) => { - if ( ! isEmpty ) { - setIsActive( true ); - } else { - setIsActive( ( prevActive ) => ! prevActive ); - } - }, - [ setIsActive ] - ); - return { options, onActive, error, setError }; -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-intents.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-intents.js deleted file mode 100644 index 86a386e72fd..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-intents.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * External dependencies - */ -import { useEffect } from '@wordpress/element'; - -/** - * @typedef {import('@woocommerce/type-defs/payment-method-interface').EmitResponseProps} EmitResponseProps - * @typedef {import('../stripe-utils/type-defs').Stripe} Stripe - */ - -/** - * Opens the modal for PaymentIntent authorizations. - * - * @param {Object} params Params object. - * @param {Stripe} params.stripe The stripe object. - * @param {Object} params.paymentDetails The payment details from the - * server after checkout processing. - * @param {string} params.errorContext Context where errors will be added. - * @param {string} params.errorType Type of error responses. - * @param {string} params.successType Type of success responses. - */ -const openIntentModal = ( { - stripe, - paymentDetails, - errorContext, - errorType, - successType, -} ) => { - const checkoutResponse = { type: successType }; - if ( - ! paymentDetails.setup_intent && - ! paymentDetails.payment_intent_secret - ) { - return checkoutResponse; - } - const isSetupIntent = !! paymentDetails.setupIntent; - const verificationUrl = paymentDetails.verification_endpoint; - const intentSecret = isSetupIntent - ? paymentDetails.setup_intent - : paymentDetails.payment_intent_secret; - return stripe[ isSetupIntent ? 'confirmCardSetup' : 'confirmCardPayment' ]( - intentSecret - ) - .then( function ( response ) { - if ( response.error ) { - throw response.error; - } - const intent = - response[ isSetupIntent ? 'setupIntent' : 'paymentIntent' ]; - if ( - intent.status !== 'requires_capture' && - intent.status !== 'succeeded' - ) { - return checkoutResponse; - } - checkoutResponse.redirectUrl = verificationUrl; - return checkoutResponse; - } ) - .catch( function ( error ) { - checkoutResponse.type = errorType; - checkoutResponse.message = error.message; - checkoutResponse.retry = true; - checkoutResponse.messageContext = errorContext; - // Reports back to the server. - window.fetch( verificationUrl + '&is_ajax' ); - return checkoutResponse; - } ); -}; - -export const usePaymentIntents = ( - stripe, - subscriber, - setSourceId, - emitResponse -) => { - useEffect( () => { - const unsubscribe = subscriber( async ( { processingResponse } ) => { - const paymentDetails = processingResponse.paymentDetails || {}; - const response = await openIntentModal( { - stripe, - paymentDetails, - errorContext: emitResponse.noticeContexts.PAYMENTS, - errorType: emitResponse.responseTypes.ERROR, - successType: emitResponse.responseTypes.SUCCESS, - } ); - if ( - response.type === emitResponse.responseTypes.ERROR && - response.retry - ) { - setSourceId( '0' ); - } - - return response; - } ); - return () => unsubscribe(); - }, [ - subscriber, - emitResponse.noticeContexts.PAYMENTS, - emitResponse.responseTypes.ERROR, - emitResponse.responseTypes.SUCCESS, - setSourceId, - stripe, - ] ); -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-processing.js b/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-processing.js deleted file mode 100644 index 8054d5cec54..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/credit-card/use-payment-processing.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * External dependencies - */ -import { useEffect } from '@wordpress/element'; -import { - CardElement, - CardNumberElement, - useElements, -} from '@stripe/react-stripe-js'; - -/** - * Internal dependencies - */ -import { PAYMENT_METHOD_NAME } from './constants'; -import { - getStripeServerData, - getErrorMessageForTypeAndCode, -} from '../stripe-utils'; -import { errorTypes } from '../stripe-utils/constants'; - -/** - * @typedef {import('@stripe/stripe-js').Stripe} Stripe - * @typedef {import('@woocommerce/type-defs/payment-method-interface').EventRegistrationProps} EventRegistrationProps - * @typedef {import('@woocommerce/type-defs/payment-method-interface').BillingDataProps} BillingDataProps - * @typedef {import('@woocommerce/type-defs/payment-method-interface').EmitResponseProps} EmitResponseProps - * @typedef {import('react').Dispatch} SourceIdDispatch - */ - -/** - * @typedef {function(function():any):function():void} EventRegistration - */ - -/** - * A custom hook that registers stripe payment processing with the - * onPaymentProcessing event from checkout. - * - * @param {function(any):string} onStripeError Sets an error for stripe. - * @param {string} error Any set error message (an empty string if no - * error). - * @param {Stripe} stripe The stripe utility - * @param {BillingDataProps} billing Various billing data items. - * @param {EmitResponseProps} emitResponse Various helpers for usage with observer - * response objects. - * @param {string} sourceId Current set stripe source id. - * @param {SourceIdDispatch} setSourceId Setter for stripe source id. - * @param {EventRegistration} onPaymentProcessing The event emitter for processing payment. - */ -export const usePaymentProcessing = ( - onStripeError, - error, - stripe, - billing, - emitResponse, - sourceId, - setSourceId, - onPaymentProcessing -) => { - const elements = useElements(); - // hook into and register callbacks for events - useEffect( () => { - const createSource = async ( ownerInfo ) => { - const elementToGet = getStripeServerData().inline_cc_form - ? CardElement - : CardNumberElement; - return await stripe.createSource( - // @ts-ignore - elements?.getElement( elementToGet ), - { - type: 'card', - owner: ownerInfo, - } - ); - }; - const onSubmit = async () => { - try { - const billingData = billing.billingData; - // if there's an error return that. - if ( error ) { - return { - type: emitResponse.responseTypes.ERROR, - message: error, - }; - } - // use token if it's set. - if ( sourceId !== '' && sourceId !== '0' ) { - return { - type: emitResponse.responseTypes.SUCCESS, - meta: { - paymentMethodData: { - paymentMethod: PAYMENT_METHOD_NAME, - paymentRequestType: 'cc', - stripe_source: sourceId, - }, - billingData, - }, - }; - } - const ownerInfo = { - address: { - line1: billingData.address_1, - line2: billingData.address_2, - city: billingData.city, - state: billingData.state, - postal_code: billingData.postcode, - country: billingData.country, - }, - }; - if ( billingData.phone ) { - ownerInfo.phone = billingData.phone; - } - if ( billingData.email ) { - ownerInfo.email = billingData.email; - } - if ( billingData.first_name || billingData.last_name ) { - ownerInfo.name = `${ billingData.first_name } ${ billingData.last_name }`; - } - - const response = await createSource( ownerInfo ); - if ( response.error ) { - return { - type: emitResponse.responseTypes.ERROR, - message: onStripeError( response ), - }; - } - if ( ! response.source || ! response.source.id ) { - throw new Error( - getErrorMessageForTypeAndCode( errorTypes.API_ERROR ) - ); - } - setSourceId( response.source.id ); - return { - type: emitResponse.responseTypes.SUCCESS, - meta: { - paymentMethodData: { - stripe_source: response.source.id, - paymentMethod: PAYMENT_METHOD_NAME, - paymentRequestType: 'cc', - }, - billingData, - }, - }; - } catch ( e ) { - return { - type: emitResponse.responseTypes.ERROR, - message: e, - }; - } - }; - const unsubscribeProcessing = onPaymentProcessing( onSubmit ); - return () => { - unsubscribeProcessing(); - }; - }, [ - onPaymentProcessing, - billing.billingData, - stripe, - sourceId, - setSourceId, - onStripeError, - error, - emitResponse.noticeContexts.PAYMENTS, - emitResponse.responseTypes.ERROR, - emitResponse.responseTypes.SUCCESS, - elements, - ] ); -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/index.js b/assets/js/payment-method-extensions/payment-methods/stripe/index.js deleted file mode 100644 index 49dd7e4fe41..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * External dependencies - */ -import { - registerPaymentMethod, - registerExpressPaymentMethod, -} from '@woocommerce/blocks-registry'; - -/** - * Internal dependencies - */ -import stripeCcPaymentMethod from './credit-card'; -import paymentRequestPaymentMethod from './payment-request'; -import { getStripeServerData } from './stripe-utils'; - -// Register Stripe Credit Card. -registerPaymentMethod( stripeCcPaymentMethod ); - -// Register Stripe Payment Request (Apple/Chrome Pay) if enabled. -if ( getStripeServerData().allowPaymentRequest ) { - registerExpressPaymentMethod( paymentRequestPaymentMethod ); -} diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/apple-pay-preview.js b/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/apple-pay-preview.js deleted file mode 100644 index cc740802ca0..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/apple-pay-preview.js +++ /dev/null @@ -1,2 +0,0 @@ -export const applePayImage = - "data:image/svg+xml,%3Csvg width='264' height='48' viewBox='0 0 264 48' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='264' height='48' rx='3' fill='black'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.114 16.6407C125.682 15.93 126.067 14.9756 125.966 14C125.135 14.0415 124.121 14.549 123.533 15.2602C123.006 15.8693 122.539 16.8641 122.661 17.7983C123.594 17.8797 124.526 17.3317 125.114 16.6407Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M125.955 17.982C124.601 17.9011 123.448 18.7518 122.801 18.7518C122.154 18.7518 121.163 18.0224 120.092 18.0421C118.696 18.0629 117.402 18.8524 116.694 20.1079C115.238 22.6196 116.31 26.3453 117.726 28.3909C118.414 29.4028 119.242 30.5174 120.334 30.4769C121.366 30.4365 121.77 29.8087 123.024 29.8087C124.277 29.8087 124.641 30.4769 125.733 30.4567C126.865 30.4365 127.573 29.4443 128.261 28.4313C129.049 27.2779 129.373 26.1639 129.393 26.1027C129.373 26.0825 127.209 25.2515 127.189 22.7606C127.169 20.6751 128.888 19.6834 128.969 19.6217C127.998 18.1847 126.481 18.0224 125.955 17.982Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M136.131 23.1804H138.834C140.886 23.1804 142.053 22.0752 142.053 20.1592C142.053 18.2432 140.886 17.1478 138.845 17.1478H136.131V23.1804ZM139.466 15.1582C142.411 15.1582 144.461 17.1903 144.461 20.1483C144.461 23.1172 142.369 25.1596 139.392 25.1596H136.131V30.3498H133.775V15.1582H139.466Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M152.198 26.224V25.3712L149.579 25.5397C148.106 25.6341 147.339 26.182 147.339 27.14C147.339 28.0664 148.138 28.6667 149.39 28.6667C150.988 28.6667 152.198 27.6449 152.198 26.224ZM145.046 27.2032C145.046 25.2551 146.529 24.1395 149.263 23.971L152.198 23.7922V22.9498C152.198 21.7181 151.388 21.0442 149.947 21.0442C148.758 21.0442 147.896 21.6548 147.717 22.5916H145.592C145.656 20.6232 147.507 19.1914 150.01 19.1914C152.703 19.1914 154.459 20.602 154.459 22.7917V30.351H152.282V28.5298H152.229C151.609 29.719 150.241 30.4666 148.758 30.4666C146.571 30.4666 145.046 29.1612 145.046 27.2032Z' fill='white'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M156.461 34.4145V32.5934C156.608 32.6141 156.965 32.6354 157.155 32.6354C158.196 32.6354 158.785 32.1932 159.142 31.0564L159.353 30.3824L155.366 19.3281H157.827L160.604 28.298H160.657L163.434 19.3281H165.832L161.698 30.9402C160.752 33.6038 159.668 34.4778 157.376 34.4778C157.197 34.4778 156.618 34.4565 156.461 34.4145Z' fill='white'/%3E%3C/svg%3E%0A"; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/constants.js b/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/constants.js deleted file mode 100644 index 25ff6bf6dd4..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/constants.js +++ /dev/null @@ -1,7 +0,0 @@ -export const PAYMENT_METHOD_NAME = 'payment_request'; - -export const DEFAULT_STRIPE_EVENT_HANDLERS = { - shippingAddressChange: null, - shippingOptionChange: null, - source: null, -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js b/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js deleted file mode 100644 index b4174ac55d8..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/index.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * External dependencies - */ -import { getSetting } from '@woocommerce/settings'; - -/** - * Internal dependencies - */ -import { PAYMENT_METHOD_NAME } from './constants'; -import { PaymentRequestExpress } from './payment-request-express'; -import { applePayImage } from './apple-pay-preview'; -import { getStripeServerData, loadStripe } from '../stripe-utils'; - -const ApplePayPreview = () => ; - -const canPayStripePromise = loadStripe(); -const componentStripePromise = loadStripe(); - -let isStripeInitialized = false, - canPay = false; - -// Initialise stripe API client and determine if payment method can be used -// in current environment (e.g. geo + shopper has payment settings configured). -function paymentRequestAvailable( { currencyCode, totalPrice } ) { - // Stripe only supports carts of greater value than 30 cents. - if ( totalPrice < 30 ) { - return false; - } - - // If we've already initialised, return the cached results. - if ( isStripeInitialized ) { - return canPay; - } - - return canPayStripePromise.then( ( stripe ) => { - if ( stripe === null ) { - isStripeInitialized = true; - return canPay; - } - if ( stripe.error && stripe.error instanceof Error ) { - throw stripe.error; - } - // Do a test payment to confirm if payment method is available. - const paymentRequest = stripe.paymentRequest( { - total: { - label: 'Total', - amount: totalPrice, - pending: true, - }, - country: getSetting( 'baseLocation', {} )?.country, - currency: currencyCode, - } ); - return paymentRequest.canMakePayment().then( ( result ) => { - canPay = !! result; - isStripeInitialized = true; - return canPay; - } ); - } ); -} - -const paymentRequestPaymentMethod = { - name: PAYMENT_METHOD_NAME, - content: , - edit: , - canMakePayment: ( cartData ) => - paymentRequestAvailable( { - currencyCode: cartData?.cartTotals?.currency_code?.toLowerCase(), - totalPrice: parseInt( cartData?.cartTotals?.total_price || 0, 10 ), - } ), - paymentMethodId: 'stripe', - supports: { - features: getStripeServerData()?.supports ?? [], - }, -}; - -export default paymentRequestPaymentMethod; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/payment-request-express.js b/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/payment-request-express.js deleted file mode 100644 index 6b98f453bf4..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/payment-request-express.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * External dependencies - */ -import { Elements, PaymentRequestButtonElement } from '@stripe/react-stripe-js'; - -/** - * Internal dependencies - */ -import { getStripeServerData } from '../stripe-utils'; -import { useInitialization } from './use-initialization'; -import { useCheckoutSubscriptions } from './use-checkout-subscriptions'; - -/** - * @typedef {import('../stripe-utils/type-defs').Stripe} Stripe - * @typedef {import('../stripe-utils/type-defs').StripePaymentRequest} StripePaymentRequest - * @typedef {import('@woocommerce/type-defs/payment-method-interface').PaymentMethodInterface} RegisteredPaymentMethodProps - */ - -/** - * @typedef {Object} WithStripe - * - * @property {Stripe} [stripe] Stripe api (might not be present) - */ - -/** - * @typedef {RegisteredPaymentMethodProps & WithStripe} StripeRegisteredPaymentMethodProps - */ - -/** - * PaymentRequestExpressComponent - * - * @param {StripeRegisteredPaymentMethodProps} props Incoming props - */ -const PaymentRequestExpressComponent = ( { - shippingData, - billing, - eventRegistration, - onSubmit, - setExpressPaymentError, - emitResponse, - onClick, - onClose, -} ) => { - const { - paymentRequest, - paymentRequestEventHandlers, - clearPaymentRequestEventHandler, - isProcessing, - canMakePayment, - onButtonClick, - abortPayment, - completePayment, - paymentRequestType, - } = useInitialization( { - billing, - shippingData, - setExpressPaymentError, - onClick, - onClose, - onSubmit, - } ); - useCheckoutSubscriptions( { - canMakePayment, - isProcessing, - eventRegistration, - paymentRequestEventHandlers, - clearPaymentRequestEventHandler, - billing, - shippingData, - emitResponse, - paymentRequestType, - completePayment, - abortPayment, - } ); - - // locale is not a valid value for the paymentRequestButton style. - const { theme } = getStripeServerData().button; - - const paymentRequestButtonStyle = { - paymentRequestButton: { - type: 'default', - theme, - height: '48px', - }, - }; - - return canMakePayment && paymentRequest ? ( - - ) : null; -}; - -/** - * PaymentRequestExpress with stripe provider - * - * @param {StripeRegisteredPaymentMethodProps} props - */ -export const PaymentRequestExpress = ( props ) => { - const { locale } = getStripeServerData().button; - const { stripe } = props; - return ( - - - - ); -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-checkout-subscriptions.js b/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-checkout-subscriptions.js deleted file mode 100644 index b74ea2fbbe7..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-checkout-subscriptions.js +++ /dev/null @@ -1,241 +0,0 @@ -/** - * External dependencies - */ -import { useEffect, useRef } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { - normalizeShippingOptions, - getTotalPaymentItem, - normalizeLineItems, - getBillingData, - getPaymentMethodData, - getShippingData, -} from '../stripe-utils'; - -/** - * @typedef {import('@woocommerce/type-defs/payment-method-interface').EventRegistrationProps} EventRegistrationProps - * @typedef {import('@woocommerce/type-defs/payment-method-interface').BillingDataProps} BillingDataProps - * @typedef {import('@woocommerce/type-defs/payment-method-interface').ShippingDataProps} ShippingDataProps - * @typedef {import('@woocommerce/type-defs/payment-method-interface').EmitResponseProps} EmitResponseProps - */ - -/** - * @param {Object} props - * - * @param {boolean} props.canMakePayment Whether the payment request - * can make payment or not. - * @param {boolean} props.isProcessing Whether the express payment - * method is processing or not. - * @param {EventRegistrationProps} props.eventRegistration Various functions for - * registering observers to - * events. - * @param {Object} props.paymentRequestEventHandlers Cached handlers registered - * for paymentRequest events. - * @param {function(string):void} props.clearPaymentRequestEventHandler Clears the cached payment - * request event handler. - * @param {BillingDataProps} props.billing - * @param {ShippingDataProps} props.shippingData - * @param {EmitResponseProps} props.emitResponse - * @param {string} props.paymentRequestType The derived payment request - * type for the express - * payment being processed. - * @param {function(any):void} props.completePayment This is a callback - * receiving the source event - * and setting it to - * successful payment. - * @param {function(any,string):any} props.abortPayment This is a callback - * receiving the source - * event and setting it to - * failed payment. - */ -export const useCheckoutSubscriptions = ( { - canMakePayment, - isProcessing, - eventRegistration, - paymentRequestEventHandlers, - clearPaymentRequestEventHandler, - billing, - shippingData, - emitResponse, - paymentRequestType, - completePayment, - abortPayment, -} ) => { - const { - onShippingRateSuccess, - onShippingRateFail, - onShippingRateSelectSuccess, - onShippingRateSelectFail, - onPaymentProcessing, - onCheckoutAfterProcessingWithSuccess, - onCheckoutAfterProcessingWithError, - } = eventRegistration; - const { noticeContexts, responseTypes } = emitResponse; - const eventHandlers = useRef( paymentRequestEventHandlers ); - const currentBilling = useRef( billing ); - const currentShipping = useRef( shippingData ); - const currentPaymentRequestType = useRef( paymentRequestType ); - - useEffect( () => { - eventHandlers.current = paymentRequestEventHandlers; - currentBilling.current = billing; - currentShipping.current = shippingData; - currentPaymentRequestType.current = paymentRequestType; - }, [ - paymentRequestEventHandlers, - billing, - shippingData, - paymentRequestType, - ] ); - - // subscribe to events. - useEffect( () => { - const onShippingRatesEvent = ( shippingRates ) => { - const handlers = eventHandlers.current; - const billingData = currentBilling.current; - if ( handlers.shippingAddressChange && isProcessing ) { - handlers.shippingAddressChange.updateWith( { - status: 'success', - shippingOptions: normalizeShippingOptions( shippingRates ), - total: getTotalPaymentItem( billingData.cartTotal ), - displayItems: normalizeLineItems( - billingData.cartTotalItems - ), - } ); - clearPaymentRequestEventHandler( 'shippingAddressChange' ); - } - }; - const onShippingRatesEventFail = ( currentErrorStatus ) => { - const handlers = eventHandlers.current; - if ( handlers.shippingAddressChange && isProcessing ) { - handlers.shippingAddressChange.updateWith( { - status: currentErrorStatus.hasInvalidAddress - ? 'invalid_shipping_address' - : 'fail', - shippingOptions: [], - } ); - } - clearPaymentRequestEventHandler( 'shippingAddressChange' ); - }; - const onShippingSelectedRate = ( forSuccess = true ) => () => { - const handlers = eventHandlers.current; - const shipping = currentShipping.current; - const billingData = currentBilling.current; - if ( - handlers.shippingOptionChange && - ! shipping.isSelectingRate && - isProcessing - ) { - const updateObject = forSuccess - ? { - status: 'success', - total: getTotalPaymentItem( billingData.cartTotal ), - displayItems: normalizeLineItems( - billingData.cartTotalItems - ), - } - : { - status: 'fail', - }; - handlers.shippingOptionChange.updateWith( updateObject ); - clearPaymentRequestEventHandler( 'shippingOptionChange' ); - } - }; - const onProcessingPayment = () => { - const handlers = eventHandlers.current; - if ( handlers.sourceEvent && isProcessing ) { - const response = { - type: responseTypes.SUCCESS, - meta: { - billingData: getBillingData( handlers.sourceEvent ), - paymentMethodData: getPaymentMethodData( - handlers.sourceEvent, - currentPaymentRequestType.current - ), - shippingData: getShippingData( handlers.sourceEvent ), - }, - }; - return response; - } - return { type: responseTypes.SUCCESS }; - }; - const onCheckoutComplete = ( checkoutResponse ) => { - const handlers = eventHandlers.current; - let response = { type: responseTypes.SUCCESS }; - if ( handlers.sourceEvent && isProcessing ) { - const { - paymentStatus, - paymentDetails, - } = checkoutResponse.processingResponse; - if ( paymentStatus === responseTypes.SUCCESS ) { - completePayment( handlers.sourceEvent ); - } - if ( - paymentStatus === responseTypes.ERROR || - paymentStatus === responseTypes.FAIL - ) { - abortPayment( handlers.sourceEvent ); - response = { - type: responseTypes.ERROR, - message: paymentDetails?.errorMessage, - messageContext: noticeContexts.EXPRESS_PAYMENTS, - retry: true, - }; - } - clearPaymentRequestEventHandler( 'sourceEvent' ); - } - return response; - }; - if ( canMakePayment && isProcessing ) { - const unsubscribeShippingRateSuccess = onShippingRateSuccess( - onShippingRatesEvent - ); - const unsubscribeShippingRateFail = onShippingRateFail( - onShippingRatesEventFail - ); - const unsubscribeShippingRateSelectSuccess = onShippingRateSelectSuccess( - onShippingSelectedRate() - ); - const unsubscribeShippingRateSelectFail = onShippingRateSelectFail( - onShippingRatesEventFail - ); - const unsubscribePaymentProcessing = onPaymentProcessing( - onProcessingPayment - ); - const unsubscribeCheckoutCompleteSuccess = onCheckoutAfterProcessingWithSuccess( - onCheckoutComplete - ); - const unsubscribeCheckoutCompleteFail = onCheckoutAfterProcessingWithError( - onCheckoutComplete - ); - return () => { - unsubscribeCheckoutCompleteFail(); - unsubscribeCheckoutCompleteSuccess(); - unsubscribePaymentProcessing(); - unsubscribeShippingRateFail(); - unsubscribeShippingRateSuccess(); - unsubscribeShippingRateSelectSuccess(); - unsubscribeShippingRateSelectFail(); - }; - } - return undefined; - }, [ - canMakePayment, - isProcessing, - onShippingRateSuccess, - onShippingRateFail, - onShippingRateSelectSuccess, - onShippingRateSelectFail, - onPaymentProcessing, - onCheckoutAfterProcessingWithSuccess, - onCheckoutAfterProcessingWithError, - responseTypes, - noticeContexts, - completePayment, - abortPayment, - clearPaymentRequestEventHandler, - ] ); -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-event-handlers.js b/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-event-handlers.js deleted file mode 100644 index 8789e5f15db..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-event-handlers.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * External dependencies - */ -import { useState, useCallback } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { DEFAULT_STRIPE_EVENT_HANDLERS } from './constants'; - -/** - * A utility hook for maintaining an event handler cache. - */ -export const useEventHandlers = () => { - const [ paymentRequestEventHandlers, setEventHandlers ] = useState( - DEFAULT_STRIPE_EVENT_HANDLERS - ); - - const setPaymentRequestEventHandler = useCallback( - ( eventName, handler ) => { - setEventHandlers( ( prevEventHandlers ) => { - return { - ...prevEventHandlers, - [ eventName ]: handler, - }; - } ); - }, - [ setEventHandlers ] - ); - - const clearPaymentRequestEventHandler = useCallback( - ( eventName ) => { - // @ts-ignore - setEventHandlers( ( prevEventHandlers ) => { - // @ts-ignore - // eslint-disable-next-line no-unused-vars - const { [ eventName ]: __, ...newHandlers } = prevEventHandlers; - return newHandlers; - } ); - }, - [ setEventHandlers ] - ); - return { - paymentRequestEventHandlers, - setPaymentRequestEventHandler, - clearPaymentRequestEventHandler, - }; -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-initialization.js b/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-initialization.js deleted file mode 100644 index 117e9cab182..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/payment-request/use-initialization.js +++ /dev/null @@ -1,251 +0,0 @@ -/** - * External dependencies - */ -import { useEffect, useState, useRef, useCallback } from '@wordpress/element'; -import { useStripe } from '@stripe/react-stripe-js'; -import { getSetting } from '@woocommerce/settings'; -import { __ } from '@wordpress/i18n'; -import isShallowEqual from '@wordpress/is-shallow-equal'; - -/** - * Internal dependencies - */ -import { - getPaymentRequest, - updatePaymentRequest, - canDoPaymentRequest, - normalizeShippingAddressForCheckout, - normalizeShippingOptionSelectionsForCheckout, - getStripeServerData, - pluckAddress, - normalizeShippingOptions, -} from '../stripe-utils'; -import { useEventHandlers } from './use-event-handlers'; - -/** - * @typedef {import('../stripe-utils/type-defs').StripePaymentRequest} StripePaymentRequest - */ - -export const useInitialization = ( { - billing, - shippingData, - setExpressPaymentError, - onClick, - onClose, - onSubmit, -} ) => { - const stripe = useStripe(); - /** - * @type {[ StripePaymentRequest|null, function( StripePaymentRequest ):void]} - */ - // @ts-ignore - const [ paymentRequest, setPaymentRequest ] = useState( null ); - const [ isFinished, setIsFinished ] = useState( false ); - const [ isProcessing, setIsProcessing ] = useState( false ); - const [ canMakePayment, setCanMakePayment ] = useState( false ); - const [ paymentRequestType, setPaymentRequestType ] = useState( '' ); - const currentShipping = useRef( shippingData ); - const { - paymentRequestEventHandlers, - clearPaymentRequestEventHandler, - setPaymentRequestEventHandler, - } = useEventHandlers(); - - // Update refs when any change. - useEffect( () => { - currentShipping.current = shippingData; - }, [ shippingData ] ); - - // Create the initial paymentRequest object. Note, we can't do anything if stripe isn't available yet or we have zero total. - useEffect( () => { - if ( - ! stripe || - ! billing.cartTotal.value || - isFinished || - isProcessing || - paymentRequest - ) { - return; - } - const pr = getPaymentRequest( { - total: billing.cartTotal, - currencyCode: billing.currency.code.toLowerCase(), - countryCode: getSetting( 'baseLocation', {} )?.country, - shippingRequired: shippingData.needsShipping, - cartTotalItems: billing.cartTotalItems, - stripe, - } ); - canDoPaymentRequest( pr ).then( ( result ) => { - setPaymentRequest( pr ); - setPaymentRequestType( result.requestType || '' ); - setCanMakePayment( result.canPay ); - } ); - }, [ - billing.cartTotal, - billing.currency.code, - shippingData.needsShipping, - billing.cartTotalItems, - stripe, - isProcessing, - isFinished, - paymentRequest, - ] ); - - // When the payment button is clicked, update the request and show it. - const onButtonClick = useCallback( () => { - setIsProcessing( true ); - setIsFinished( false ); - setExpressPaymentError( '' ); - updatePaymentRequest( { - // @ts-ignore - paymentRequest, - total: billing.cartTotal, - currencyCode: billing.currency.code.toLowerCase(), - cartTotalItems: billing.cartTotalItems, - } ); - onClick(); - }, [ - onClick, - paymentRequest, - setExpressPaymentError, - billing.cartTotal, - billing.currency.code, - billing.cartTotalItems, - ] ); - - const abortPayment = useCallback( ( paymentMethod ) => { - paymentMethod.complete( 'fail' ); - setIsProcessing( false ); - setIsFinished( true ); - }, [] ); - - const completePayment = useCallback( ( paymentMethod ) => { - paymentMethod.complete( 'success' ); - setIsFinished( true ); - setIsProcessing( false ); - }, [] ); - - // whenever paymentRequest changes, hook in event listeners. - useEffect( () => { - const noop = { removeAllListeners: () => void null }; - let shippingAddressChangeEvent = noop, - shippingOptionChangeEvent = noop, - sourceChangeEvent = noop, - cancelChangeEvent = noop; - - if ( paymentRequest ) { - const cancelHandler = () => { - setIsFinished( false ); - setIsProcessing( false ); - setPaymentRequest( null ); - onClose(); - }; - - const shippingAddressChangeHandler = ( event ) => { - const newShippingAddress = normalizeShippingAddressForCheckout( - event.shippingAddress - ); - if ( - isShallowEqual( - pluckAddress( newShippingAddress ), - pluckAddress( currentShipping.current.shippingAddress ) - ) - ) { - // the address is the same so no change needed. - event.updateWith( { - status: 'success', - shippingOptions: normalizeShippingOptions( - currentShipping.current.shippingRates - ), - } ); - } else { - // the address is different so let's set the new address and - // register the handler to be picked up by the shipping rate - // change event. - currentShipping.current.setShippingAddress( - normalizeShippingAddressForCheckout( - event.shippingAddress - ) - ); - setPaymentRequestEventHandler( - 'shippingAddressChange', - event - ); - } - }; - - const shippingOptionChangeHandler = ( event ) => { - currentShipping.current.setSelectedRates( - normalizeShippingOptionSelectionsForCheckout( - event.shippingOption - ) - ); - setPaymentRequestEventHandler( 'shippingOptionChange', event ); - }; - - const sourceHandler = ( paymentMethod ) => { - if ( - // eslint-disable-next-line no-undef - ! getStripeServerData().allowPrepaidCard && - paymentMethod.source.card.funding - ) { - setExpressPaymentError( - /* eslint-disable-next-line @wordpress/i18n-text-domain */ - __( - "Sorry, we're not accepting prepaid cards at this time.", - 'woocommerce-gateway-stripe' - ) - ); - return; - } - setPaymentRequestEventHandler( 'sourceEvent', paymentMethod ); - // kick off checkout processing step. - onSubmit(); - }; - - // @ts-ignore - shippingAddressChangeEvent = paymentRequest.on( - 'shippingaddresschange', - shippingAddressChangeHandler - ); - // @ts-ignore - shippingOptionChangeEvent = paymentRequest.on( - 'shippingoptionchange', - shippingOptionChangeHandler - ); - // @ts-ignore - sourceChangeEvent = paymentRequest.on( 'source', sourceHandler ); - // @ts-ignore - cancelChangeEvent = paymentRequest.on( 'cancel', cancelHandler ); - } - - return () => { - if ( paymentRequest ) { - shippingAddressChangeEvent.removeAllListeners(); - shippingOptionChangeEvent.removeAllListeners(); - sourceChangeEvent.removeAllListeners(); - cancelChangeEvent.removeAllListeners(); - } - }; - }, [ - paymentRequest, - canMakePayment, - isProcessing, - setPaymentRequestEventHandler, - setExpressPaymentError, - onSubmit, - onClose, - ] ); - - return { - paymentRequest, - paymentRequestEventHandlers, - clearPaymentRequestEventHandler, - isProcessing, - canMakePayment, - onButtonClick, - abortPayment, - completePayment, - paymentRequestType, - }; -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/constants.js b/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/constants.js deleted file mode 100644 index 7257f91cca3..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/constants.js +++ /dev/null @@ -1,28 +0,0 @@ -export const errorTypes = { - INVALID_EMAIL: 'email_invalid', - INVALID_REQUEST: 'invalid_request_error', - API_CONNECTION: 'api_connection_error', - API_ERROR: 'api_error', - AUTHENTICATION_ERROR: 'authentication_error', - RATE_LIMIT_ERROR: 'rate_limit_error', - CARD_ERROR: 'card_error', - VALIDATION_ERROR: 'validation_error', -}; - -export const errorCodes = { - INVALID_NUMBER: 'invalid_number', - INVALID_EXPIRY_MONTH: 'invalid_expiry_month', - INVALID_EXPIRY_YEAR: 'invalid_expiry_year', - INVALID_CVC: 'invalid_cvc', - INCORRECT_NUMBER: 'incorrect_number', - INCOMPLETE_NUMBER: 'incomplete_number', - INCOMPLETE_CVC: 'incomplete_cvc', - INCOMPLETE_EXPIRY: 'incomplete_expiry', - EXPIRED_CARD: 'expired_card', - INCORRECT_CVC: 'incorrect_cvc', - INCORRECT_ZIP: 'incorrect_zip', - INVALID_EXPIRY_YEAR_PAST: 'invalid_expiry_year_past', - CARD_DECLINED: 'card_declined', - MISSING: 'missing', - PROCESSING_ERROR: 'processing_error', -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/index.js b/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/index.js deleted file mode 100644 index 6bbd44578f6..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export * from './normalize'; -export * from './utils'; -export * from './load-stripe'; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/load-stripe.js b/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/load-stripe.js deleted file mode 100644 index da46b583390..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/load-stripe.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * External dependencies - */ -import { loadStripe } from '@stripe/stripe-js'; - -/** - * Internal dependencies - */ -import { getApiKey } from './utils'; - -const stripePromise = () => - new Promise( ( resolve ) => { - try { - resolve( loadStripe( getApiKey() ) ); - } catch ( error ) { - // In order to avoid showing console error publicly to users, - // we resolve instead of rejecting when there is an error. - resolve( { error } ); - } - } ); - -export { stripePromise as loadStripe }; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/normalize.js b/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/normalize.js deleted file mode 100644 index ef66e7b648a..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/normalize.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @typedef {import('./type-defs').StripePaymentItem} StripePaymentItem - * @typedef {import('./type-defs').StripeShippingOption} StripeShippingOption - * @typedef {import('./type-defs').StripeShippingAddress} StripeShippingAddress - * @typedef {import('./type-defs').StripePaymentResponse} StripePaymentResponse - * @typedef {import('@woocommerce/type-defs/payment-method-interface').PreparedCartTotalItem} CartTotalItem - * @typedef {import('@woocommerce/type-defs/cart').CartShippingOption} CartShippingOption - * @typedef {import('@woocommerce/type-defs/shipping').ShippingAddress} CartShippingAddress - * @typedef {import('@woocommerce/type-defs/billing').BillingData} CartBillingAddress - */ - -/** - * Normalizes incoming cart total items for use as a displayItems with the - * Stripe api. - * - * @param {CartTotalItem[]} cartTotalItems CartTotalItems to normalize - * @param {boolean} pending Whether to mark items as pending or - * not - * - * @return {StripePaymentItem[]} An array of PaymentItems - */ -const normalizeLineItems = ( cartTotalItems, pending = false ) => { - return cartTotalItems - .map( ( cartTotalItem ) => { - return cartTotalItem.value - ? { - amount: cartTotalItem.value, - label: cartTotalItem.label, - pending, - } - : false; - } ) - .filter( Boolean ); -}; - -/** - * Normalizes incoming cart shipping option items for use as shipping options - * with the Stripe api. - * - * @param {CartShippingOption[]} shippingOptions An array of CartShippingOption items. - * - * @return {StripeShippingOption[]} An array of Stripe shipping option items. - */ -const normalizeShippingOptions = ( shippingOptions ) => { - const rates = shippingOptions[ 0 ].shipping_rates; - return rates.map( ( rate ) => { - return { - id: rate.rate_id, - label: rate.name, - detail: rate.description, - amount: parseInt( rate.price, 10 ), - }; - } ); -}; - -/** - * Normalize shipping address information from stripe's address object to - * the cart shipping address object shape. - * - * @param {StripeShippingAddress} shippingAddress Stripe's shipping address item - * - * @return {CartShippingAddress} The shipping address in the shape expected by - * the cart. - */ -const normalizeShippingAddressForCheckout = ( shippingAddress ) => { - const address = { - first_name: shippingAddress.recipient - .split( ' ' ) - .slice( 0, 1 ) - .join( ' ' ), - last_name: shippingAddress.recipient - .split( ' ' ) - .slice( 1 ) - .join( ' ' ), - company: '', - address_1: - typeof shippingAddress.addressLine[ 0 ] === 'undefined' - ? '' - : shippingAddress.addressLine[ 0 ], - address_2: - typeof shippingAddress.addressLine[ 1 ] === 'undefined' - ? '' - : shippingAddress.addressLine[ 1 ], - city: shippingAddress.city, - state: shippingAddress.region, - country: shippingAddress.country, - postcode: shippingAddress.postalCode.replace( ' ', '' ), - }; - return address; -}; - -/** - * Normalizes shipping option shape selection from Stripe's shipping option - * object to the expected shape for cart shipping option selections. - * - * @param {StripeShippingOption} shippingOption The customer's selected shipping - * option. - * - * @return {string[]} An array of ids (in this case will just be one) - */ -const normalizeShippingOptionSelectionsForCheckout = ( shippingOption ) => { - return shippingOption.id; -}; - -/** - * Returns the billing data extracted from the stripe payment response to the - * CartBillingData shape. - * - * @param {StripePaymentResponse} paymentResponse Stripe's payment response - * object. - * - * @return {CartBillingAddress} The cart billing data - */ -const getBillingData = ( paymentResponse ) => { - const source = paymentResponse.source; - const name = source && source.owner.name; - const billing = source && source.owner.address; - const payerEmail = paymentResponse.payerEmail || ''; - const payerPhone = paymentResponse.payerPhone || ''; - return { - first_name: name ? name.split( ' ' ).slice( 0, 1 ).join( ' ' ) : '', - last_name: name ? name.split( ' ' ).slice( 1 ).join( ' ' ) : '', - email: ( source && source.owner.email ) || payerEmail, - phone: - ( source && source.owner.phone ) || - payerPhone.replace( '/[() -]/g', '' ), - country: ( billing && billing.country ) || '', - address_1: ( billing && billing.line1 ) || '', - address_2: ( billing && billing.line2 ) || '', - city: ( billing && billing.city ) || '', - state: ( billing && billing.state ) || '', - postcode: ( billing && billing.postal_code ) || '', - company: '', - }; -}; - -/** - * This returns extra payment method data to add to the payment method update - * request made by the checkout processor. - * - * @param {StripePaymentResponse} paymentResponse A stripe payment response - * object. - * @param {string} paymentRequestType The payment request type - * used for payment. - * - * @return {Object} An object with the extra payment data. - */ -const getPaymentMethodData = ( paymentResponse, paymentRequestType ) => { - return { - payment_method: 'stripe', - stripe_source: paymentResponse.source - ? paymentResponse.source.id - : null, - payment_request_type: paymentRequestType, - }; -}; - -const getShippingData = ( paymentResponse ) => { - return paymentResponse.shippingAddress - ? { - address: normalizeShippingAddressForCheckout( - paymentResponse.shippingAddress - ), - } - : null; -}; - -export { - normalizeLineItems, - normalizeShippingOptions, - normalizeShippingAddressForCheckout, - normalizeShippingOptionSelectionsForCheckout, - getBillingData, - getPaymentMethodData, - getShippingData, -}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/type-defs.js b/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/type-defs.js deleted file mode 100644 index 22c10d128dc..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/type-defs.js +++ /dev/null @@ -1,324 +0,0 @@ -/** - * Stripe PaymentItem object - * - * @typedef {Object} StripePaymentItem - * - * @property {string} label The label for the payment item. - * @property {number} amount The amount for the payment item (in subunits) - * @property {boolean} [pending] Whether or not the amount is pending update on - * recalculation. - */ - -/** - * Stripe ShippingOption object - * - * @typedef {Object} StripeShippingOption - * - * @property {string} id A unique ID for the shipping option. - * @property {string} label A short label for the shipping option. - * @property {string} detail A longer description for the shipping option. - * @property {number} amount The amount to show for the shipping option - * (in subunits) - */ - -/** - * @typedef {Object} StripeShippingAddress - * - * @property {string} country Two letter country code, capitalized - * (ISO3166 alpha-2). - * @property {Array} addressLine An array of address line items. - * @property {string} region The most coarse subdivision of a - * country. (state etc) - * @property {string} city The name of a city, town, village etc. - * @property {string} postalCode The postal or ZIP code. - * @property {string} recipient The name of the recipient. - * @property {string} phone The phone number of the recipient. - * @property {string} [sortingCode] The sorting code as used in France. - * Not present on Apple platforms. - * @property {string} [dependentLocality] A logical subdivision of a city. - * Not present on Apple platforms. - */ - -/** - * @typedef {Object} StripeBillingDetails - * - * @property {Object} address The billing address - * @property {string} address.city The billing address city - * @property {string} address.country The billing address country - * @property {string} address.line1 The first line for the address - * @property {string} address.line2 The second line fro the address - * @property {string} address.postal_code The postal/zip code - * @property {string} address.state The state - * @property {string} email The billing email - * @property {string} name The billing name - * @property {string} phone The billing phone - * @property {Object} [verified_address] The verified address of the owner. - * @property {string} [verified_email] Provided by the payment provider. - * @property {string} [verified_phone] Provided by the payment provider. - * @property {string} [verified_name] Provided by the payment provider. - */ - -/** - * @typedef {Object} StripeBillingCard - * - * @property {string} brand The card brand - * @property {Object} checks Various security checks - * @property {string} checks.address_line1_check If an address line1 was - * provided, results of the - * check. - * @property {string} checks.address_postal_code_check If a postal code was - * provided, results of the - * check. - * @property {string} checks.cvc_check If CVC provided, results - * of the check. - * @property {string} country Two-letter ISO code for - * the country on the card. - * @property {number} exp_month Two-digit number for - * card expiry month. - * @property {number} exp_year Two-digit number for - * card expiry year. - * @property {string} fingerprint Uniquely identifies this - * particular card number - * @property {string} funding The card funding type - * @property {Object} generated_from Details of the original - * PaymentMethod that - * created this object. - * @property {string} last4 The last 4 digits of the - * card - * @property {Object} three_d_secure_usage Contains details on how - * this card may be used for - * 3d secure - * @property {Object} wallet If this card is part of a - * card wallet, this - * contains the details of - * the card wallet. - */ - -/** - * @typedef {Object} StripePaymentMethod - * - * @property {string} id Unique identifier for the - * object - * @property {StripeBillingDetails} billing_details The billing details for the - * payment method - * @property {StripeBillingCard} card Details on the card used to - * pay - * @property {string} customer The ID of the customer to - * which this payment method - * is saved. - * @property {Object} metadata Set of key-value pairs that - * can be attached to the - * object. - * @property {string} type Type of payment method - * @property {string} object The type of object. Always - * 'payment_method'. Can use - * to validate! - * @property {Object} card_present If this is a card present - * payment method, contains - * details about that card - * @property {number} created The timestamp for when the - * card was created. - * @property {Object} fpx If this is an fpx payment - * method, contains details - * about it. - * @property {Object} ideal If this is an ideal payment - * method, contains details - * about it. - * @property {boolean} livemode True if the object exists - * in live mode or if in test - * mode. - * @property {Object} sepa_debit If this is a sepa_debit - * payment method, contains - * details about it. - */ - -/** - * @typedef {Object} StripeSource - * - * @property {string} id Unique identifier for - * object - * @property {number} amount A positive number in - * the smallest currency - * unit. - * @property {string} currency The three-letter ISO - * code for the currency - * @property {string} customer The ID of the customer - * to which this source - * is attached. - * @property {Object} metadata Arbitrary key-value - * pairs that can be - * attached. - * @property {StripeBillingDetails} owner Information about the - * owner of the payment - * made. - * @property {Object} [redirect] Information related to - * the redirect flow - * (present if the source - * is authenticated by - * redirect) - * @property {string} statement_descriptor Extra information - * about a source (will - * appear on customer's - * statement) - * @property {string} status The status of the - * source. - * @property {string} type The type of the source - * (it is a payment - * method type) - * @property {string} object Value is "source" can - * be used to validate. - * @property {string} client_secret The client secret of - * the source. Used for - * client-side retrieval - * using a publishable - * key. - * @property {Object} [code_verification] Information related to - * the code verification - * flow. - * @property {number} created When the source object - * was instantiated - * (timestamp). - * @property {string} flow The authentication - * flow of the source. - * @property {boolean} livemode If true then payment - * is made in live mode - * otherwise test mode. - * @property {Object} [receiver] Information related to - * the receiver flow. - * @property {Object} source_order Information about the - * items and shipping - * associated with the - * source. - * @property {string} usage Whether source should - * be reusable or not. - */ - -/** - * @typedef {Object} StripePaymentResponse - * - * @property {Object} token A stripe token object - * @property {StripePaymentMethod} paymentMethod The stripe payment method - * object - * @property {?StripeSource} source Present if this was the - * result of a source event - * listener - * @property {Function} complete Call this when the token - * data has been processed. - * @property {string} [payerName] The customer's name. - * @property {string} [payerEmail] The customer's email. - * @property {string} [payerPhone] The customer's phone. - * @property {StripeShippingAddress} [shippingAddress] The final shipping - * address the customer - * indicated - * @property {StripeShippingOption} [shippingOption] The final shipping - * option the customer - * selected. - * @property {string} methodName The unique name of the - * payment handler the - * customer chose to - * authorize payment - */ - -/** - * @typedef {Object} StripePaymentRequestOptions The configuration of stripe - * payment request options to - * pass in. - * - * @property {string} country Two-letter (ISO) - * country code. - * @property {string} currency Three letter currency - * code. - * @property {StripePaymentItem} total Shown to the customer. - * @property {StripePaymentItem[]} displayItems Line items shown to the - * customer. - * @property {boolean} requestPayerName Whether or not to - * collect the payer's - * name. - * @property {boolean} requestPayerEmail Whether or not to - * collect the payer's - * email. - * @property {boolean} requestPayerPhone Whether or not to - * collect the payer's - * phone. - * @property {boolean} requestShipping Whether to collect - * shipping address. - * @property {StripeShippingOption[]} shippingOptions Available shipping - * options. - */ - -/** - * @typedef {Object} StripePaymentRequest Stripe payment request object. - * - * @property {function():Promise} canMakePayment Returns a promise that resolves - * with an object detailing if a - * browser payment API is - * available. - * @property {function()} show Shows the browser's payment - * interface (called automatically - * if payment request button in - * use) - * @property {function()} update Used to update a PaymentRequest - * object. - * @property {function()} on For registering callbacks on - * payment request events. - */ - -/** - * @typedef {Object} Stripe Stripe api object. - * @property {any} api Various api properties - */ - -/** - * @typedef {Object} CreditCardIcon - * - * @property {string} url Url to icon. - * @property {string} alt Alt text for icon. - */ - -/* eslint-disable jsdoc/valid-types */ -// [k:string]:CreditCardIcon triggers the above rule even though VSCode interprets it fine. -/** - * @typedef {Object} StripeServerData - * - * @property {string} stripeTotalLabel The string used for payment - * descriptor. - * @property {string} publicKey The public api key for stripe - * requests. - * @property {boolean} allowPrepaidCard True means that prepaid cards - * can be used for payment. - * @property {Object} button Contains button styles - * @property {string} button.type The type of button. - * @property {string} button.theme The theme for the button. - * @property {string} button.height The height (in pixels) for - * the button. - * @property {string} button.locale The locale to use for stripe - * elements. - * @property {boolean} inline_cc_form Whether stripe cc should use - * inline cc - * form or separate inputs. - * @property {{[k:string]:CreditCardIcon}} icons Contains supported cc icons. - * @property {boolean} showSavedCards Used to indicate whether saved cards - * can be used. - * @property {boolean} showSaveOption Used to indicate whether the option to - * save card can be displayed. - * @property {boolean} allowPaymentRequest True if merchant has enabled payment - * request (Chrome/Apple Pay). - * @property {Object} supports List of features supported by the payment gateway - */ -/* eslint-enable jsdoc/valid-types */ - -/** - * @typedef {Object} StripeElementOptions - * - * @property {Object} options The configuration object for stripe - * elements. - * @property {function(boolean)} onActive A callback for setting whether an - * element is active or not. "Active" - * means it's not empty. - * @property {string} error Any error message from the stripe - * element. - * @property {function(string)} setError A callback for setting an error - * message. - */ - -export {}; diff --git a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/utils.js b/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/utils.js deleted file mode 100644 index 74f071107fc..00000000000 --- a/assets/js/payment-method-extensions/payment-methods/stripe/stripe-utils/utils.js +++ /dev/null @@ -1,281 +0,0 @@ -/** - * External dependencies - */ -import { getSetting } from '@woocommerce/settings'; -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { normalizeLineItems } from './normalize'; -import { errorTypes, errorCodes } from './constants'; - -/** - * @typedef {import('./type-defs').StripeServerData} StripeServerData - * @typedef {import('./type-defs').StripePaymentItem} StripePaymentItem - * @typedef {import('./type-defs').StripePaymentRequest} StripePaymentRequest - * @typedef {import('@woocommerce/type-defs/payment-method-interface').PreparedCartTotalItem} CartTotalItem - */ - -/** - * Stripe data comes form the server passed on a global object. - * - * @return {StripeServerData} Stripe server data. - */ -const getStripeServerData = () => { - const stripeServerData = getSetting( 'stripe_data', null ); - if ( ! stripeServerData ) { - throw new Error( 'Stripe initialization data is not available' ); - } - return stripeServerData; -}; - -/** - * Returns the public api key for the stripe payment method - * - * @throws Error - * @return {string} The public api key for the stripe payment method. - */ -const getApiKey = () => { - const apiKey = getStripeServerData().publicKey; - if ( ! apiKey ) { - throw new Error( - 'There is no api key available for stripe. Make sure it is available on the wc.stripe_data.stripe.key property.' - ); - } - return apiKey; -}; - -/** - * The total PaymentItem object used for the stripe PaymentRequest object. - * - * @param {CartTotalItem} total The total amount. - * - * @return {StripePaymentItem} The PaymentItem object used for stripe. - */ -const getTotalPaymentItem = ( total ) => { - return { - label: - getStripeServerData().stripeTotalLabel || - __( 'Total', 'woo-gutenberg-products-block' ), - amount: total.value, - }; -}; - -/** - * Returns a stripe payment request object - * - * @param {Object} config A configuration object for - * getting the payment request. - * @param {Object} config.stripe The stripe api. - * @param {CartTotalItem} config.total The amount for the total - * (in subunits) provided by - * checkout/cart. - * @param {string} config.currencyCode The currency code provided - * by checkout/cart. - * @param {string} config.countryCode The country code provided by - * checkout/cart. - * @param {boolean} config.shippingRequired Whether or not shipping is - * required. - * @param {CartTotalItem[]} config.cartTotalItems Array of line items provided - * by checkout/cart. - * - * @return {StripePaymentRequest} A stripe payment request object - */ -const getPaymentRequest = ( { - stripe, - total, - currencyCode, - countryCode, - shippingRequired, - cartTotalItems, -} ) => { - const options = { - total: getTotalPaymentItem( total ), - currency: currencyCode, - country: countryCode || 'US', - requestPayerName: true, - requestPayerEmail: true, - requestPayerPhone: true, - requestShipping: shippingRequired, - displayItems: normalizeLineItems( cartTotalItems ), - }; - return stripe.paymentRequest( options ); -}; - -/** - * Utility function for updating the Stripe PaymentRequest object - * - * @param {Object} update An object containing the - * things needed for the - * update - * @param {StripePaymentRequest} update.paymentRequest A Stripe payment request - * object - * @param {CartTotalItem} update.total A total line item. - * @param {string} update.currencyCode The currency code for the - * amount provided. - * @param {CartTotalItem[]} update.cartTotalItems An array of line items - * provided by the - * cart/checkout. - */ -const updatePaymentRequest = ( { - paymentRequest, - total, - currencyCode, - cartTotalItems, -} ) => { - paymentRequest.update( { - total: getTotalPaymentItem( total ), - currency: currencyCode, - displayItems: normalizeLineItems( cartTotalItems ), - } ); -}; - -/** - * Returns whether or not the current session can do apple pay. - * - * @param {StripePaymentRequest} paymentRequest A Stripe PaymentRequest instance. - * - * @return {Promise} True means apple pay can be done. - */ -const canDoPaymentRequest = ( paymentRequest ) => { - return new Promise( ( resolve ) => { - paymentRequest.canMakePayment().then( ( result ) => { - if ( result ) { - const paymentRequestType = result.applePay - ? 'apple_pay' - : 'payment_request_api'; - resolve( { canPay: true, requestType: paymentRequestType } ); - return; - } - resolve( { canPay: false } ); - } ); - } ); -}; - -const isNonFriendlyError = ( type ) => - [ - errorTypes.INVALID_REQUEST, - errorTypes.API_CONNECTION, - errorTypes.API_ERROR, - errorTypes.AUTHENTICATION_ERROR, - errorTypes.RATE_LIMIT_ERROR, - ].includes( type ); - -const getErrorMessageForCode = ( code ) => { - const messages = { - /* eslint-disable @wordpress/i18n-text-domain */ - [ errorCodes.INVALID_NUMBER ]: __( - 'The card number is not a valid credit card number.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INVALID_EXPIRY_MONTH ]: __( - 'The card expiration month is invalid.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INVALID_EXPIRY_YEAR ]: __( - 'The card expiration year is invalid.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INVALID_CVC ]: __( - 'The card security code is invalid.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INCORRECT_NUMBER ]: __( - 'The card number is incorrect.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INCOMPLETE_NUMBER ]: __( - 'The card number is incomplete.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INCOMPLETE_CVC ]: __( - 'The card security code is incomplete.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INCOMPLETE_EXPIRY ]: __( - 'The card expiration date is incomplete.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.EXPIRED_CARD ]: __( - 'The card has expired.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INCORRECT_CVC ]: __( - 'The card security code is incorrect.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INCORRECT_ZIP ]: __( - 'The card zip code failed validation.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.INVALID_EXPIRY_YEAR_PAST ]: __( - 'The card expiration year is in the past', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.CARD_DECLINED ]: __( - 'The card was declined.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.MISSING ]: __( - 'There is no card on a customer that is being charged.', - 'woocommerce-gateway-stripe' - ), - [ errorCodes.PROCESSING_ERROR ]: __( - 'An error occurred while processing the card.', - 'woocommerce-gateway-stripe' - ), - /* eslint-enable @wordpress/i18n-text-domain */ - }; - return messages[ code ] || null; -}; - -const getErrorMessageForTypeAndCode = ( type, code = '' ) => { - switch ( type ) { - case errorTypes.INVALID_EMAIL: - return __( - 'Invalid email address, please correct and try again.', - 'woo-gutenberg-products-block' - ); - case isNonFriendlyError( type ): - return __( - 'Unable to process this payment, please try again or use alternative method.', - 'woo-gutenberg-products-block' - ); - case errorTypes.CARD_ERROR: - return getErrorMessageForCode( code ); - case errorTypes.VALIDATION_ERROR: - return ''; // These are shown inline. - } - return null; -}; - -/** - * pluckAddress takes a full address object and returns relevant fields for calculating - * shipping, so we can track when one of them change to update rates. - * - * @param {Object} address An object containing all address information - * @param {string} address.country - * @param {string} address.state - * @param {string} address.city - * @param {string} address.postcode - * - * @return {Object} pluckedAddress An object containing shipping address that are needed to fetch an address. - */ -const pluckAddress = ( { country, state, city, postcode } ) => ( { - country, - state, - city, - postcode: postcode.replace( ' ', '' ).toUpperCase(), -} ); - -export { - getStripeServerData, - getApiKey, - getTotalPaymentItem, - getPaymentRequest, - updatePaymentRequest, - canDoPaymentRequest, - getErrorMessageForTypeAndCode, - pluckAddress, -}; diff --git a/assets/js/previews/saved-payment-methods.js b/assets/js/previews/saved-payment-methods.js index de6d047b0cb..2a848d6ace5 100644 --- a/assets/js/previews/saved-payment-methods.js +++ b/assets/js/previews/saved-payment-methods.js @@ -2,7 +2,7 @@ export const previewSavedPaymentMethods = { cc: [ { method: { - gateway: 'stripe', + gateway: 'credit-card', last4: '5678', brand: 'Visa', }, diff --git a/assets/js/types/type-defs/payments.ts b/assets/js/types/type-defs/payments.ts index 8dba7172373..af0478e6efc 100644 --- a/assets/js/types/type-defs/payments.ts +++ b/assets/js/types/type-defs/payments.ts @@ -69,7 +69,7 @@ export interface PaymentMethodConfiguration { paymentMethodId?: string; // Object that describes various features provided by the payment method. supports: SupportsConfiguration; - // Array of card types (brands) supported by the payment method. (See stripe/credit-card for example.) + // Array of card types (brands) supported by the payment method. icons?: null | PaymentMethodIcons; // A react node that will be used as a label for the payment method in the checkout. label: ReactNode; diff --git a/bin/hook-docs/data/filters.json b/bin/hook-docs/data/filters.json index 7ed46163cbb..7c319bbd862 100644 --- a/bin/hook-docs/data/filters.json +++ b/bin/hook-docs/data/filters.json @@ -83,100 +83,6 @@ }, "args": 2 }, - { - "name": "wc_stripe_allow_prepaid_card", - "file": "Payments/Integrations/Stripe.php", - "type": "filter", - "doc": { - "description": "Filters if prepaid cards are supported by Stripe.", - "long_description": "", - "tags": [ - { - "name": "param", - "content": "True if prepaid cards are allowed.", - "types": [ - "boolean" - ], - "variable": "$allow_prepaid_card" - }, - { - "name": "return", - "content": "", - "types": [ - "boolean" - ] - } - ], - "long_description_html": "" - }, - "args": 1 - }, - { - "name": "wc_stripe_display_save_payment_method_checkbox", - "file": "Payments/Integrations/Stripe.php", - "type": "filter", - "doc": { - "description": "Filters if the save payment method checkbox is shown for Stripe.", - "long_description": "This assumes that Stripe supports `tokenization` - currently this is true, based on https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95", - "tags": [ - { - "name": "see", - "content": "", - "refers": "https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271" - }, - { - "name": "see", - "content": "", - "refers": "https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API" - }, - { - "name": "param", - "content": "True if saved cards functionality is enabled.", - "types": [ - "boolean" - ], - "variable": "$saved_cards" - }, - { - "name": "return", - "content": "", - "types": [ - "boolean" - ] - } - ], - "long_description_html": "

This assumes that Stripe supports tokenization - currently this is true, based on https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95

" - }, - "args": 1 - }, - { - "name": "wc_stripe_payment_request_button_locale", - "file": "Payments/Integrations/Stripe.php", - "type": "filter", - "doc": { - "description": "Filters the payment request button locale.", - "long_description": "", - "tags": [ - { - "name": "param", - "content": "Current locale. Defaults to en_US.", - "types": [ - "string" - ], - "variable": "$locale" - }, - { - "name": "return", - "content": "", - "types": [ - "string" - ] - } - ], - "long_description_html": "" - }, - "args": 1 - }, { "name": "woocommerce_add_cart_item", "file": "StoreApi/Utilities/CartController.php", diff --git a/bin/webpack-entries.js b/bin/webpack-entries.js index 6f4ef3506a8..3c4d256809e 100644 --- a/bin/webpack-entries.js +++ b/bin/webpack-entries.js @@ -134,8 +134,6 @@ const entries = { './assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx', }, payments: { - 'wc-payment-method-stripe': - './assets/js/payment-method-extensions/payment-methods/stripe/index.js', 'wc-payment-method-cheque': './assets/js/payment-method-extensions/payment-methods/cheque/index.js', 'wc-payment-method-paypal': diff --git a/docs/contributors/folder-structure.md b/docs/contributors/folder-structure.md index 8eff8780641..567bd1a224c 100644 --- a/docs/contributors/folder-structure.md +++ b/docs/contributors/folder-structure.md @@ -128,7 +128,7 @@ The following snippet explains how the WooCommerce Blocks repository is structur │ The middleware code to handle Store API calls. │ ├── assets/js/payment-method-extensions - │ Functionality for the payment options such as PayPal and Stripe. + │ Functionality for the payment options such as PayPal. │ ├── assets/js/previews │ The previews of various components such the All Products Block. diff --git a/docs/extensibility/filters.md b/docs/extensibility/filters.md index e269bc8a0d0..4a47c98f11c 100644 --- a/docs/extensibility/filters.md +++ b/docs/extensibility/filters.md @@ -10,9 +10,6 @@ - [__experimental_woocommerce_blocks_add_data_attributes_to_block](#__experimental_woocommerce_blocks_add_data_attributes_to_block) - [__experimental_woocommerce_blocks_add_data_attributes_to_namespace](#__experimental_woocommerce_blocks_add_data_attributes_to_namespace) - [__experimental_woocommerce_blocks_payment_gateway_features_list](#__experimental_woocommerce_blocks_payment_gateway_features_list) - - [wc_stripe_allow_prepaid_card](#wc_stripe_allow_prepaid_card) - - [wc_stripe_display_save_payment_method_checkbox](#wc_stripe_display_save_payment_method_checkbox) - - [wc_stripe_payment_request_button_locale](#wc_stripe_payment_request_button_locale) - [woocommerce_add_cart_item](#woocommerce_add_cart_item) - [woocommerce_add_cart_item_data](#woocommerce_add_cart_item_data) - [woocommerce_add_to_cart_sold_individually_quantity](#woocommerce_add_to_cart_sold_individually_quantity) @@ -133,97 +130,6 @@ add_filter( '__experimental_woocommerce_blocks_payment_gateway_features_list', ' --- -## wc_stripe_allow_prepaid_card - - -Filters if prepaid cards are supported by Stripe. - -```php -apply_filters( 'wc_stripe_allow_prepaid_card', boolean $allow_prepaid_card ) -``` - -### Parameters - -| Argument | Type | Description | -| -------- | ---- | ----------- | -| $allow_prepaid_card | boolean | True if prepaid cards are allowed. | - -### Returns - - -`boolean` - -### Source - - - - [Payments/Integrations/Stripe.php](../src/Payments/Integrations/Stripe.php) - ---- - -## wc_stripe_display_save_payment_method_checkbox - - -Filters if the save payment method checkbox is shown for Stripe. - -```php -apply_filters( 'wc_stripe_display_save_payment_method_checkbox', boolean $saved_cards ) -``` - -### Description - -

This assumes that Stripe supports tokenization - currently this is true, based on https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95

- -### Parameters - -| Argument | Type | Description | -| -------- | ---- | ----------- | -| $saved_cards | boolean | True if saved cards functionality is enabled. | - -### Returns - - -`boolean` - -### See - - - - https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271 - - https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API - -### Source - - - - [Payments/Integrations/Stripe.php](../src/Payments/Integrations/Stripe.php) - ---- - -## wc_stripe_payment_request_button_locale - - -Filters the payment request button locale. - -```php -apply_filters( 'wc_stripe_payment_request_button_locale', string $locale ) -``` - -### Parameters - -| Argument | Type | Description | -| -------- | ---- | ----------- | -| $locale | string | Current locale. Defaults to en_US. | - -### Returns - - -`string` - -### Source - - - - [Payments/Integrations/Stripe.php](../src/Payments/Integrations/Stripe.php) - ---- - ## woocommerce_add_cart_item diff --git a/docs/testing/smoke-testing.md b/docs/testing/smoke-testing.md index 06e7c44bdd8..392f2d492f5 100644 --- a/docs/testing/smoke-testing.md +++ b/docs/testing/smoke-testing.md @@ -131,6 +131,6 @@ In the `wp:woocommerce/product-search` substitute the URL used for the `action` * [ ] Do critical flows for the Cart and Checkout blocks work? * [ ] Address and shipping calculations * [ ] Payment with core payment methods - * [ ] Payment with Stripe and saved payment methods + * [ ] Payment with Stripe (extension) and saved payment methods * [ ] Payment with Express payment methods (Chrome Pay or Apple Pay) * [ ] Make sure you test with logged in user and in browser incognito mode. diff --git a/package-lock.json b/package-lock.json index 77428903972..9d744815d5e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,6 @@ "hasInstallScript": true, "license": "GPL-3.0+", "dependencies": { - "@stripe/react-stripe-js": "1.6.0", - "@stripe/stripe-js": "1.16.0", "@wordpress/autop": "3.2.3", "@wordpress/deprecated": "3.2.3", "@wordpress/icons": "6.1.1", @@ -7340,24 +7338,6 @@ "node": ">=8" } }, - "node_modules/@stripe/react-stripe-js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.6.0.tgz", - "integrity": "sha512-tMmsPD+wkpiiVJZgQ1E06tklG5MZHG462s6OWja9abpxq76kerAxMFN+KdhUg0LIEY79THbzvH3s/WGHasnV3w==", - "dependencies": { - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "@stripe/stripe-js": "^1.19.1", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/@stripe/stripe-js": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.16.0.tgz", - "integrity": "sha512-ZSHbiwTrISoaTbpercmYGuY7QTg7HxfFyNgbJBaYbwHWbzMhpEdGTsmMpaBXIU6iiqwEEDaIyD8O6yJ+H5DWCg==" - }, "node_modules/@stylelint/postcss-css-in-js": { "version": "0.37.2", "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", @@ -46739,19 +46719,6 @@ } } }, - "@stripe/react-stripe-js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-1.6.0.tgz", - "integrity": "sha512-tMmsPD+wkpiiVJZgQ1E06tklG5MZHG462s6OWja9abpxq76kerAxMFN+KdhUg0LIEY79THbzvH3s/WGHasnV3w==", - "requires": { - "prop-types": "^15.7.2" - } - }, - "@stripe/stripe-js": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-1.16.0.tgz", - "integrity": "sha512-ZSHbiwTrISoaTbpercmYGuY7QTg7HxfFyNgbJBaYbwHWbzMhpEdGTsmMpaBXIU6iiqwEEDaIyD8O6yJ+H5DWCg==" - }, "@stylelint/postcss-css-in-js": { "version": "0.37.2", "resolved": "https://registry.npmjs.org/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz", diff --git a/package.json b/package.json index cee251a61fb..74acfd12e8a 100644 --- a/package.json +++ b/package.json @@ -193,8 +193,6 @@ "npm": "^8.0.0" }, "dependencies": { - "@stripe/react-stripe-js": "1.6.0", - "@stripe/stripe-js": "1.16.0", "@wordpress/autop": "3.2.3", "@wordpress/deprecated": "3.2.3", "@wordpress/icons": "6.1.1", diff --git a/src/Domain/Bootstrap.php b/src/Domain/Bootstrap.php index d25b2e3334c..9617df9b902 100644 --- a/src/Domain/Bootstrap.php +++ b/src/Domain/Bootstrap.php @@ -12,7 +12,6 @@ use Automattic\WooCommerce\Blocks\RestApi; use Automattic\WooCommerce\Blocks\Payments\Api as PaymentsApi; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; -use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe; use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque; use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal; use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer; @@ -300,18 +299,8 @@ function ( Container $container ) { /** * Register payment method integrations with the container. - * - * @internal Stripe is a temporary method that is used for setting up payment method integrations with Cart and - * Checkout blocks. This logic should get moved to the payment gateway extensions. */ protected function register_payment_methods() { - $this->container->register( - Stripe::class, - function( Container $container ) { - $asset_api = $container->get( AssetApi::class ); - return new Stripe( $asset_api ); - } - ); $this->container->register( Cheque::class, function( Container $container ) { diff --git a/src/Payments/Api.php b/src/Payments/Api.php index 72a2bbe0e70..d6154fdcd7f 100644 --- a/src/Payments/Api.php +++ b/src/Payments/Api.php @@ -4,7 +4,6 @@ use Automattic\WooCommerce\Blocks\Package; use Automattic\WooCommerce\Blocks\Assets\AssetDataRegistry; use Automattic\WooCommerce\Blocks\StoreApi\Utilities\NoticeHandler; -use Automattic\WooCommerce\Blocks\Payments\Integrations\Stripe; use Automattic\WooCommerce\Blocks\Payments\Integrations\Cheque; use Automattic\WooCommerce\Blocks\Payments\Integrations\PayPal; use Automattic\WooCommerce\Blocks\Payments\Integrations\BankTransfer; @@ -105,12 +104,6 @@ public function add_payment_method_script_data() { * @param PaymentMethodRegistry $payment_method_registry Payment method registry instance. */ public function register_payment_method_integrations( PaymentMethodRegistry $payment_method_registry ) { - // This is temporarily registering Stripe until it's moved to the extension. - if ( class_exists( '\WC_Stripe', false ) && ! $payment_method_registry->is_registered( 'stripe' ) ) { - $payment_method_registry->register( - Package::container()->get( Stripe::class ) - ); - } $payment_method_registry->register( Package::container()->get( Cheque::class ) ); diff --git a/src/Payments/Integrations/Stripe.php b/src/Payments/Integrations/Stripe.php deleted file mode 100644 index b6594a90372..00000000000 --- a/src/Payments/Integrations/Stripe.php +++ /dev/null @@ -1,367 +0,0 @@ -asset_api = $asset_api; - add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_payment_request_order_meta' ], 8, 2 ); - add_action( 'woocommerce_rest_checkout_process_payment_with_context', [ $this, 'add_stripe_intents' ], 9999, 2 ); - } - - /** - * Initializes the payment method type. - */ - public function initialize() { - $this->settings = get_option( 'woocommerce_stripe_settings', [] ); - } - - /** - * Returns if this payment method should be active. If false, the scripts will not be enqueued. - * - * @return boolean - */ - public function is_active() { - return ! empty( $this->settings['enabled'] ) && 'yes' === $this->settings['enabled']; - } - - /** - * Returns an array of scripts/handles to be registered for this payment method. - * - * @return array - */ - public function get_payment_method_script_handles() { - $this->asset_api->register_script( - 'wc-payment-method-stripe', - 'build/wc-payment-method-stripe.js', - [] - ); - - return [ 'wc-payment-method-stripe' ]; - } - - /** - * Returns an array of key=>value pairs of data made available to the payment methods script. - * - * @return array - */ - public function get_payment_method_data() { - return [ - 'stripeTotalLabel' => $this->get_total_label(), - 'publicKey' => $this->get_publishable_key(), - 'allowPrepaidCard' => $this->get_allow_prepaid_card(), - 'title' => $this->get_title(), - 'button' => [ - 'type' => $this->get_button_type(), - 'theme' => $this->get_button_theme(), - 'height' => $this->get_button_height(), - 'locale' => $this->get_button_locale(), - ], - 'inline_cc_form' => $this->get_inline_cc_form(), - 'icons' => $this->get_icons(), - 'showSavedCards' => $this->get_show_saved_cards(), - 'allowPaymentRequest' => $this->get_allow_payment_request(), - 'showSaveOption' => $this->get_show_save_option(), - 'supports' => $this->get_supported_features(), - ]; - } - - /** - * Determine if store allows cards to be saved during checkout. - * - * @return bool True if merchant allows shopper to save card (payment method) during checkout). - */ - private function get_show_saved_cards() { - return isset( $this->settings['saved_cards'] ) ? 'yes' === $this->settings['saved_cards'] : false; - } - - /** - * Determine if the checkbox to enable the user to save their payment method should be shown. - * - * @return bool True if the save payment checkbox should be displayed to the user. - */ - private function get_show_save_option() { - $saved_cards = $this->get_show_saved_cards(); - /** - * Filters if the save payment method checkbox is shown for Stripe. - * - * This assumes that Stripe supports `tokenization` - currently this is true, based on https://github.com/woocommerce/woocommerce-gateway-stripe/blob/master/includes/class-wc-gateway-stripe.php#L95 - * - * @see https://github.com/woocommerce/woocommerce-gateway-stripe/blob/ad19168b63df86176cbe35c3e95203a245687640/includes/class-wc-gateway-stripe.php#L271 - * @see https://github.com/woocommerce/woocommerce/wiki/Payment-Token-API - * - * @param boolean $saved_cards True if saved cards functionality is enabled. - * @return boolean - */ - return apply_filters( 'wc_stripe_display_save_payment_method_checkbox', filter_var( $saved_cards, FILTER_VALIDATE_BOOLEAN ) ); - } - - /** - * Returns the label to use accompanying the total in the stripe statement. - * - * @return string Statement descriptor. - */ - private function get_total_label() { - return ! empty( $this->settings['statement_descriptor'] ) ? WC_Stripe_Helper::clean_statement_descriptor( $this->settings['statement_descriptor'] ) : ''; - } - - /** - * Returns the publishable api key for the Stripe service. - * - * @return string Public api key. - */ - private function get_publishable_key() { - $test_mode = ( ! empty( $this->settings['testmode'] ) && 'yes' === $this->settings['testmode'] ); - $setting_key = $test_mode ? 'test_publishable_key' : 'publishable_key'; - return ! empty( $this->settings[ $setting_key ] ) ? $this->settings[ $setting_key ] : ''; - } - - /** - * Returns whether to allow prepaid cards for payments. - * - * @return bool True means to allow prepaid card (default). - */ - private function get_allow_prepaid_card() { - /** - * Filters if prepaid cards are supported by Stripe. - * - * @param boolean $allow_prepaid_card True if prepaid cards are allowed. - * @return boolean - */ - return apply_filters( 'wc_stripe_allow_prepaid_card', true ); - } - - /** - * Returns the title string to use in the UI (customisable via admin settings screen). - * - * @return string Title / label string - */ - private function get_title() { - return isset( $this->settings['title'] ) ? $this->settings['title'] : __( 'Credit / Debit Card', 'woo-gutenberg-products-block' ); - } - - /** - * Determine if store allows Payment Request buttons - e.g. Apple Pay / Chrome Pay. - * - * @return bool True if merchant has opted into payment request. - */ - private function get_allow_payment_request() { - $option = isset( $this->settings['payment_request'] ) ? $this->settings['payment_request'] : false; - return filter_var( $option, FILTER_VALIDATE_BOOLEAN ); - } - - /** - * Return the button type for the payment button. - * - * @return string Defaults to 'default'. - */ - private function get_button_type() { - return isset( $this->settings['payment_request_button_type'] ) ? $this->settings['payment_request_button_type'] : 'default'; - } - - /** - * Return the theme to use for the payment button. - * - * @return string Defaults to 'dark'. - */ - private function get_button_theme() { - return isset( $this->settings['payment_request_button_theme'] ) ? $this->settings['payment_request_button_theme'] : 'dark'; - } - - /** - * Return the height for the payment button. - * - * @return string A pixel value for the height (defaults to '64'). - */ - private function get_button_height() { - return isset( $this->settings['payment_request_button_height'] ) ? str_replace( 'px', '', $this->settings['payment_request_button_height'] ) : '64'; - } - - /** - * Return the inline cc option. - * - * @return boolean True if the inline CC form option is enabled. - */ - private function get_inline_cc_form() { - return isset( $this->settings['inline_cc_form'] ) && 'yes' === $this->settings['inline_cc_form']; - } - - /** - * Return the locale for the payment button. - * - * @return string Defaults to en_US. - */ - private function get_button_locale() { - /** - * Filters the payment request button locale. - * - * @param string $locale Current locale. Defaults to en_US. - * @return string - */ - return apply_filters( 'wc_stripe_payment_request_button_locale', substr( get_locale(), 0, 2 ) ); - } - - /** - * Return the icons urls. - * - * @return array Arrays of icons metadata. - */ - private function get_icons() { - $icons_src = [ - 'visa' => [ - 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/visa.svg', - 'alt' => __( 'Visa', 'woo-gutenberg-products-block' ), - ], - 'amex' => [ - 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/amex.svg', - 'alt' => __( 'American Express', 'woo-gutenberg-products-block' ), - ], - 'mastercard' => [ - 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/mastercard.svg', - 'alt' => __( 'Mastercard', 'woo-gutenberg-products-block' ), - ], - ]; - - if ( 'USD' === get_woocommerce_currency() ) { - $icons_src['discover'] = [ - 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/discover.svg', - 'alt' => __( 'Discover', 'woo-gutenberg-products-block' ), - ]; - $icons_src['jcb'] = [ - 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/jcb.svg', - 'alt' => __( 'JCB', 'woo-gutenberg-products-block' ), - ]; - $icons_src['diners'] = [ - 'src' => WC_STRIPE_PLUGIN_URL . '/assets/images/diners.svg', - 'alt' => __( 'Diners', 'woo-gutenberg-products-block' ), - ]; - } - return $icons_src; - } - - /** - * Add payment request data to the order meta as hooked on the - * woocommerce_rest_checkout_process_payment_with_context action. - * - * @param PaymentContext $context Holds context for the payment. - * @param PaymentResult $result Result object for the payment. - */ - public function add_payment_request_order_meta( PaymentContext $context, PaymentResult &$result ) { - $data = $context->payment_data; - if ( ! empty( $data['payment_request_type'] ) && 'stripe' === $context->payment_method ) { - // phpcs:ignore WordPress.Security.NonceVerification - $post_data = $_POST; - $_POST = $context->payment_data; - $this->add_order_meta( $context->order, $data['payment_request_type'] ); - $_POST = $post_data; - } - - // hook into stripe error processing so that we can capture the error to - // payment details (which is added to notices and thus not helpful for - // this context). - if ( 'stripe' === $context->payment_method ) { - add_action( - 'wc_gateway_stripe_process_payment_error', - function( $error ) use ( &$result ) { - $payment_details = $result->payment_details; - $payment_details['errorMessage'] = wp_strip_all_tags( $error->getLocalizedMessage() ); - $result->set_payment_details( $payment_details ); - } - ); - } - } - - /** - * Handles any potential stripe intents on the order that need handled. - * - * This is configured to execute after legacy payment processing has - * happened on the woocommerce_rest_checkout_process_payment_with_context - * action hook. - * - * @param PaymentContext $context Holds context for the payment. - * @param PaymentResult $result Result object for the payment. - */ - public function add_stripe_intents( PaymentContext $context, PaymentResult &$result ) { - if ( 'stripe' === $context->payment_method - && ( - ! empty( $result->payment_details['payment_intent_secret'] ) - || ! empty( $result->payment_details['setup_intent_secret'] ) - ) - ) { - $payment_details = $result->payment_details; - $payment_details['verification_endpoint'] = add_query_arg( - [ - 'order' => $context->order->get_id(), - 'nonce' => wp_create_nonce( 'wc_stripe_confirm_pi' ), - 'redirect_to' => rawurlencode( $result->redirect_url ), - ], - home_url() . \WC_Ajax::get_endpoint( 'wc_stripe_verify_intent' ) - ); - $result->set_payment_details( $payment_details ); - $result->set_status( 'success' ); - } - } - - /** - * Handles adding information about the payment request type used to the order meta. - * - * @param \WC_Order $order The order being processed. - * @param string $payment_request_type The payment request type used for payment. - */ - private function add_order_meta( \WC_Order $order, string $payment_request_type ) { - if ( 'apple_pay' === $payment_request_type ) { - $order->set_payment_method_title( 'Apple Pay (Stripe)' ); - $order->save(); - } - - if ( 'payment_request_api' === $payment_request_type ) { - $order->set_payment_method_title( 'Chrome Payment Request (Stripe)' ); - $order->save(); - } - } - - /** - * Returns an array of supported features. - * - * @return string[] - */ - public function get_supported_features() { - $gateway = new WC_Gateway_Stripe(); - return array_filter( $gateway->supports, array( $gateway, 'supports' ) ); - } -} From a5353c1ddb13407622207be5300d61f80857690a Mon Sep 17 00:00:00 2001 From: Raluca Stan Date: Tue, 11 Jan 2022 11:58:51 +0100 Subject: [PATCH 22/31] Update patch-initial-checklist.md (#5538) Update the patch release checklist to include the email confirmation step --- .github/patch-initial-checklist.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/patch-initial-checklist.md b/.github/patch-initial-checklist.md index 764ac328149..e3714d7eae1 100644 --- a/.github/patch-initial-checklist.md +++ b/.github/patch-initial-checklist.md @@ -40,6 +40,7 @@ Additionally, make sure to differentiate between things in the testing notes tha * [ ] Execute `npm run deploy` * Note: the script automatically updates version numbers (commits on your behalf). * **ALERT**: This script will ask you if this release will be deployed to WordPress.org. You should only answer yes for this release **if it's the latest release and you want to deploy to WordPress.org**. Otherwise, answer no. If you answer yes, you will get asked additional verification by the `npm run deploy` script about deploying a patch release to WordPress.org. + * An email confirmation is required before the new version will be released, so check your email in order to confirm the release. ## If this release is deployed to WordPress.org... From aa78099bb15bf5bfdce42cf6b423a391365a6992 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Tue, 11 Jan 2022 11:09:59 +0000 Subject: [PATCH 23/31] Add minimum quantity, maximum quantity, and step (multiple_of) to the Cart Block and Store API (#5406) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add min and step to Store API * add min and step support * typo * Update assets/js/base/components/quantity-selector/index.tsx * Update assets/js/base/components/quantity-selector/index.tsx * Fix debounce callback * Style qty input to show steps * Implement quantity_limits in API * Quantity validation * Update product API * Normalize on + - * Separate add to cart events from cart item events in regards to limits * Prevent qty change for editable line items * Unify filters * Remove step number indicator from buttons ¯\_(ツ)_/¯ * Normalize on mount * Update docs Co-authored-by: Nadir Seghir --- .../add-to-cart/product-types/simple.js | 2 + .../product-types/variable/index.js | 2 + .../add-to-cart/shared/quantity-input.js | 48 +- .../components/quantity-selector/index.tsx | 98 +++- .../components/quantity-selector/style.scss | 6 +- .../add-to-cart-form/form-state/constants.js | 2 +- .../add-to-cart-form/form-state/index.js | 8 +- .../cart-line-item-row.tsx | 41 +- .../js/shared/context/product-data-context.js | 4 +- assets/js/types/type-defs/cart-response.ts | 33 +- assets/js/types/type-defs/cart.ts | 7 +- assets/js/types/type-defs/product-response.ts | 4 +- bin/hook-docs/data/actions.json | 2 +- bin/hook-docs/data/filters.json | 47 +- docs/extensibility/actions.md | 4 +- docs/extensibility/filters.md | 36 +- src/StoreApi/Routes/CartAddItem.php | 12 +- src/StoreApi/Schemas/CartItemSchema.php | 62 +-- src/StoreApi/Schemas/ProductSchema.php | 66 +-- src/StoreApi/Utilities/CartController.php | 53 ++- src/StoreApi/Utilities/QuantityLimits.php | 211 ++++++++ src/StoreApi/docs/cart-items.md | 449 +++++++++--------- src/StoreApi/docs/cart.md | 14 +- 23 files changed, 815 insertions(+), 396 deletions(-) create mode 100644 src/StoreApi/Utilities/QuantityLimits.php diff --git a/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/simple.js b/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/simple.js index a08fe69ee38..ce3670bcd36 100644 --- a/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/simple.js +++ b/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/simple.js @@ -18,6 +18,7 @@ const Simple = () => { quantity, minQuantity, maxQuantity, + multipleOf, dispatchActions, isDisabled, } = useAddToCartFormContext(); @@ -43,6 +44,7 @@ const Simple = () => { value={ quantity } min={ minQuantity } max={ maxQuantity } + step={ multipleOf } disabled={ isDisabled } onChange={ dispatchActions.setQuantity } /> diff --git a/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/variable/index.js b/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/variable/index.js index fb7d707e306..4cc5e9e16fd 100644 --- a/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/variable/index.js +++ b/assets/js/atomic/blocks/product-elements/add-to-cart/product-types/variable/index.js @@ -23,6 +23,7 @@ const Variable = () => { quantity, minQuantity, maxQuantity, + multipleOf, dispatchActions, isDisabled, } = useAddToCartFormContext(); @@ -52,6 +53,7 @@ const Variable = () => { value={ quantity } min={ minQuantity } max={ maxQuantity } + step={ multipleOf } disabled={ isDisabled } onChange={ dispatchActions.setQuantity } /> diff --git a/assets/js/atomic/blocks/product-elements/add-to-cart/shared/quantity-input.js b/assets/js/atomic/blocks/product-elements/add-to-cart/shared/quantity-input.js index 5f275ab98ac..a58a7250ada 100644 --- a/assets/js/atomic/blocks/product-elements/add-to-cart/shared/quantity-input.js +++ b/assets/js/atomic/blocks/product-elements/add-to-cart/shared/quantity-input.js @@ -1,3 +1,8 @@ +/** + * External dependencies + */ +import { useDebouncedCallback } from 'use-debounce'; + /** * Quantity Input Component. * @@ -5,10 +10,49 @@ * @param {boolean} props.disabled Whether input is disabled or not. * @param {number} props.min Minimum value for input. * @param {number} props.max Maximum value for input. + * @param {number} props.step Step attribute for input. * @param {number} props.value Value for input. * @param {function():any} props.onChange Function to call on input change event. */ -const QuantityInput = ( { disabled, min, max, value, onChange } ) => { +const QuantityInput = ( { disabled, min, max, step = 1, value, onChange } ) => { + const hasMaximum = typeof max !== 'undefined'; + + /** + * The goal of this function is to normalize what was inserted, + * but after the customer has stopped typing. + * + * It's important to wait before normalizing or we end up with + * a frustrating experience, for example, if the minimum is 2 and + * the customer is trying to type "10", premature normalizing would + * always kick in at "1" and turn that into 2. + * + * Copied from + */ + const normalizeQuantity = useDebouncedCallback( ( initialValue ) => { + // We copy the starting value. + let newValue = initialValue; + + // We check if we have a maximum value, and select the lowest between what was inserted and the maximum. + if ( hasMaximum ) { + newValue = Math.min( + newValue, + // the maximum possible value in step increments. + Math.floor( max / step ) * step + ); + } + + // Select the biggest between what's inserted, the the minimum value in steps. + newValue = Math.max( newValue, Math.ceil( min / step ) * step ); + + // We round off the value to our steps. + newValue = Math.floor( newValue / step ) * step; + + // Only commit if the value has changed + if ( newValue !== initialValue ) { + onChange( newValue ); + } + }, 300 ); + return ( { value={ value } min={ min } max={ max } + step={ step } hidden={ max === 1 } disabled={ disabled } onChange={ ( e ) => { onChange( e.target.value ); + normalizeQuantity( e.target.value ); } } /> ); diff --git a/assets/js/base/components/quantity-selector/index.tsx b/assets/js/base/components/quantity-selector/index.tsx index 516740236c4..1076c3c6dd4 100644 --- a/assets/js/base/components/quantity-selector/index.tsx +++ b/assets/js/base/components/quantity-selector/index.tsx @@ -4,8 +4,9 @@ import { __, sprintf } from '@wordpress/i18n'; import { speak } from '@wordpress/a11y'; import classNames from 'classnames'; -import { useCallback } from '@wordpress/element'; +import { useCallback, useLayoutEffect } from '@wordpress/element'; import { DOWN, UP } from '@wordpress/keycodes'; +import { useDebouncedCallback } from 'use-debounce'; /** * Internal dependencies @@ -31,6 +32,10 @@ export interface QuantitySelectorProps { * Maximum quantity */ maximum: number; + /** + * Input step attribute. + */ + step?: number; /** * Event handler triggered when the quantity is changed */ @@ -53,6 +58,7 @@ const QuantitySelector = ( { minimum = 1, maximum, onChange = () => void 0, + step = 1, itemName = '', disabled, }: QuantitySelectorProps ): JSX.Element => { @@ -62,8 +68,59 @@ const QuantitySelector = ( { ); const hasMaximum = typeof maximum !== 'undefined'; - const canDecrease = quantity > minimum; - const canIncrease = ! hasMaximum || quantity < maximum; + const canDecrease = quantity - step >= minimum; + const canIncrease = ! hasMaximum || quantity + step <= maximum; + + /** + * The goal of this function is to normalize what was inserted, + * but after the customer has stopped typing. + */ + const normalizeQuantity = useCallback( + ( initialValue: number ) => { + // We copy the starting value. + let value = initialValue; + + // We check if we have a maximum value, and select the lowest between what was inserted and the maximum. + if ( hasMaximum ) { + value = Math.min( + value, + // the maximum possible value in step increments. + Math.floor( maximum / step ) * step + ); + } + + // Select the biggest between what's inserted, the the minimum value in steps. + value = Math.max( value, Math.ceil( minimum / step ) * step ); + + // We round off the value to our steps. + value = Math.floor( value / step ) * step; + + // Only commit if the value has changed + if ( value !== initialValue ) { + onChange( value ); + } + }, + [ hasMaximum, maximum, minimum, onChange, step ] + ); + + /* + * It's important to wait before normalizing or we end up with + * a frustrating experience, for example, if the minimum is 2 and + * the customer is trying to type "10", premature normalizing would + * always kick in at "1" and turn that into 2. + */ + const debouncedNormalizeQuantity = useDebouncedCallback( + normalizeQuantity, + // This value is deliberately smaller than what's in useStoreCartItemQuantity so we don't end up with two requests. + 300 + ); + + /** + * Normalize qty on mount before render. + */ + useLayoutEffect( () => { + normalizeQuantity( quantity ); + }, [ quantity, normalizeQuantity ] ); /** * Handles keyboard up and down keys to change quantity value. @@ -83,15 +140,15 @@ const QuantitySelector = ( { if ( isArrowDown && canDecrease ) { event.preventDefault(); - onChange( quantity - 1 ); + onChange( quantity - step ); } if ( isArrowUp && canIncrease ) { event.preventDefault(); - onChange( quantity + 1 ); + onChange( quantity + step ); } }, - [ quantity, onChange, canIncrease, canDecrease ] + [ quantity, onChange, canIncrease, canDecrease, step ] ); return ( @@ -100,22 +157,23 @@ const QuantitySelector = ( { className="wc-block-components-quantity-selector__input" disabled={ disabled } type="number" - step="1" - min="0" + step={ step } + min={ minimum } + max={ maximum } value={ quantity } onKeyDown={ quantityInputOnKeyDown } onChange={ ( event ) => { - let value = - Number.isNaN( event.target.value ) || - ! event.target.value - ? 0 - : parseInt( event.target.value, 10 ); - if ( hasMaximum ) { - value = Math.min( value, maximum ); - } - value = Math.max( value, minimum ); + // Inputs values are strings, we parse them here. + let value = parseInt( event.target.value, 10 ); + // parseInt would throw NaN for anything not a number, + // so we revert value to the quantity value. + value = isNaN( value ) ? quantity : value; + if ( value !== quantity ) { + // we commit this value immediately. onChange( value ); + // but once the customer has stopped typing, we make sure his value is respecting the bounds (maximum value, minimum value, step value), and commit the normalized value. + debouncedNormalizeQuantity( value ); } } } aria-label={ sprintf( @@ -135,7 +193,7 @@ const QuantitySelector = ( { className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--minus" disabled={ disabled || ! canDecrease } onClick={ () => { - const newQuantity = quantity - 1; + const newQuantity = quantity - step; onChange( newQuantity ); speak( sprintf( @@ -147,6 +205,7 @@ const QuantitySelector = ( { newQuantity ) ); + normalizeQuantity( newQuantity ); } } > - @@ -159,7 +218,7 @@ const QuantitySelector = ( { disabled={ disabled || ! canIncrease } className="wc-block-components-quantity-selector__button wc-block-components-quantity-selector__button--plus" onClick={ () => { - const newQuantity = quantity + 1; + const newQuantity = quantity + step; onChange( newQuantity ); speak( sprintf( @@ -171,6 +230,7 @@ const QuantitySelector = ( { newQuantity ) ); + normalizeQuantity( newQuantity ); } } > + diff --git a/assets/js/base/components/quantity-selector/style.scss b/assets/js/base/components/quantity-selector/style.scss index 3a7095e9eea..a2ab45f775f 100644 --- a/assets/js/base/components/quantity-selector/style.scss +++ b/assets/js/base/components/quantity-selector/style.scss @@ -41,6 +41,7 @@ line-height: 1; vertical-align: middle; -moz-appearance: textfield; + font-weight: 600; &:focus { background: $gray-100; @@ -70,11 +71,12 @@ .wc-block-components-quantity-selector__button { @include reset-button; - @include font-size(regular); + @include font-size(regular, 0.9em); min-width: 30px; cursor: pointer; - color: $gray-900; + color: $gray-600; font-style: normal; + font-weight: normal; text-align: center; text-decoration: none; diff --git a/assets/js/base/context/providers/add-to-cart-form/form-state/constants.js b/assets/js/base/context/providers/add-to-cart-form/form-state/constants.js index 9f3633ced93..c9ebea0b330 100644 --- a/assets/js/base/context/providers/add-to-cart-form/form-state/constants.js +++ b/assets/js/base/context/providers/add-to-cart-form/form-state/constants.js @@ -13,7 +13,7 @@ export const STATUS = { export const DEFAULT_STATE = { status: STATUS.PRISTINE, hasError: false, - quantity: 1, + quantity: 0, processingResponse: null, requestParams: {}, }; diff --git a/assets/js/base/context/providers/add-to-cart-form/form-state/index.js b/assets/js/base/context/providers/add-to-cart-form/form-state/index.js index 4a409eb5f9b..b8438f0b46b 100644 --- a/assets/js/base/context/providers/add-to-cart-form/form-state/index.js +++ b/assets/js/base/context/providers/add-to-cart-form/form-state/index.js @@ -296,9 +296,11 @@ export const AddToCartFormStateContextProvider = ( { productHasOptions: product.has_options || false, supportsFormElements, showFormElements: showFormElements && supportsFormElements, - quantity: addToCartFormState.quantity, - minQuantity: 1, - maxQuantity: product.quantity_limit || 99, + quantity: + addToCartFormState.quantity || product?.add_to_cart?.minimum || 1, + minQuantity: product?.add_to_cart?.minimum || 1, + maxQuantity: product?.add_to_cart?.maximum || 99, + multipleOf: product?.add_to_cart?.multiple_of || 1, requestParams: addToCartFormState.requestParams, isIdle: addToCartFormState.status === STATUS.IDLE, isDisabled: addToCartFormState.status === STATUS.DISABLED, diff --git a/assets/js/blocks/cart-checkout/cart/cart-line-items-table/cart-line-item-row.tsx b/assets/js/blocks/cart-checkout/cart/cart-line-items-table/cart-line-item-row.tsx index 94bc2b0e05d..26894b8250a 100644 --- a/assets/js/blocks/cart-checkout/cart/cart-line-items-table/cart-line-item-row.tsx +++ b/assets/js/blocks/cart-checkout/cart/cart-line-items-table/cart-line-item-row.tsx @@ -70,7 +70,13 @@ const CartLineItemRow = forwardRef< HTMLTableRowElement, CartLineItemRowProps >( description: fullDescription = '', low_stock_remaining: lowStockRemaining = null, show_backorder_badge: showBackorderBadge = false, - quantity_limit: quantityLimit = 99, + quantity_limits: quantityLimits = { + minimum: 1, + maximum: 99, + multiple_of: 1, + editable: true, + }, + sold_individually: soldIndividually = false, permalink = '', images = [], variation = [], @@ -278,19 +284,26 @@ const CartLineItemRow = forwardRef< HTMLTableRowElement, CartLineItemRowProps >( />
- { - setItemQuantity( newQuantity ); - dispatchStoreEvent( 'cart-set-item-quantity', { - product: lineItem, - quantity: newQuantity, - } ); - } } - itemName={ name } - /> + { ! soldIndividually && !! quantityLimits.editable && ( + { + setItemQuantity( newQuantity ); + dispatchStoreEvent( + 'cart-set-item-quantity', + { + product: lineItem, + quantity: newQuantity, + } + ); + } } + itemName={ name } + /> + ) }