@automattic/newspack-blocks 4.30.3 → 4.30.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## @automattic/newspack-blocks [4.30.4](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.30.3...newspack-blocks@4.30.4) (2026-08-25)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **blocks:** track modal purchases made via the Store API ([#881](https://github.com/Automattic/newspack-workspace/issues/881)) ([06cd0a9](https://github.com/Automattic/newspack-workspace/commit/06cd0a95946703497a8f2bba0ecfd06c69a09718))
7
+
1
8
  ## @automattic/newspack-blocks [4.30.3](https://github.com/Automattic/newspack-workspace/compare/newspack-blocks@4.30.2...newspack-blocks@4.30.3) (2026-08-20)
2
9
 
3
10
 
@@ -1621,13 +1621,18 @@ final class Modal_Checkout {
1621
1621
  /**
1622
1622
  * Return URL for modal checkout "thank you" page.
1623
1623
  *
1624
+ * Origin detection covers the referer so that express-wallet (Apple Pay /
1625
+ * Google Pay) Store API submissions — JSON bodies with no request params —
1626
+ * get a decorated return URL and land on the modal thank-you, where the
1627
+ * front-end GA4 purchase event fires.
1628
+ *
1624
1629
  * @param string $url The URL to redirect to.
1625
1630
  * @param WC_Order $order The order related to the transaction.
1626
1631
  *
1627
1632
  * @return string
1628
1633
  */
1629
1634
  public static function woocommerce_get_return_url( $url, $order ) {
1630
- if ( ! self::is_modal_checkout() || self::has_unsupported_payment_gateway() ) {
1635
+ if ( ! self::is_modal_checkout_origin() || self::has_unsupported_payment_gateway() ) {
1631
1636
  return $url;
1632
1637
  }
1633
1638
 
@@ -1946,7 +1951,7 @@ final class Modal_Checkout {
1946
1951
  * @return array
1947
1952
  */
1948
1953
  public static function relax_configured_off_locale_fields( $locale ) {
1949
- if ( ! self::is_modal_checkout_referer() && ! self::is_modal_checkout() ) {
1954
+ if ( ! self::is_modal_checkout_origin() ) {
1950
1955
  return $locale;
1951
1956
  }
1952
1957
 
@@ -2309,6 +2314,11 @@ final class Modal_Checkout {
2309
2314
 
2310
2315
  /**
2311
2316
  * Is this request using the modal checkout?
2317
+ *
2318
+ * Detects modal request *data* (request params, serialized post_data, or a
2319
+ * classic express-checkout submission). For origin detection that also
2320
+ * covers parameter-less Store API JSON submissions, see
2321
+ * is_modal_checkout_origin().
2312
2322
  */
2313
2323
  public static function is_modal_checkout() {
2314
2324
  // Until we use the modal checkout flow from My Account, we don't want to show the modal checkout thank you template for checkouts originating from My Account.
@@ -2328,6 +2338,31 @@ final class Modal_Checkout {
2328
2338
  return $is_modal_checkout;
2329
2339
  }
2330
2340
 
2341
+ /**
2342
+ * Does this request originate from the modal checkout?
2343
+ *
2344
+ * Superset of is_modal_checkout(): additionally true for Store API JSON
2345
+ * submissions (express wallets such as Apple Pay and Google Pay) whose only
2346
+ * modal signal is the referer query, since JSON bodies carry no request
2347
+ * params and leave $_POST empty.
2348
+ *
2349
+ * The referer is client-controlled, so this gates analytics and
2350
+ * presentation decisions only — never authorization.
2351
+ *
2352
+ * Known edge: a wallet submission from the My Account-origin modal reads as
2353
+ * modal-origin, because the modal strips the my_account_checkout marker
2354
+ * from every URL it opens (src/modal-checkout/modal.js). Since #2121 that
2355
+ * is also how My Account card checkouts behave, so wallet and card flows
2356
+ * stay in parity; the My Account exclusion inside is_modal_checkout()
2357
+ * continues to govern the legacy non-modal flows, whose referers never
2358
+ * carry modal_checkout.
2359
+ *
2360
+ * @return bool
2361
+ */
2362
+ public static function is_modal_checkout_origin() {
2363
+ return self::is_modal_checkout() || self::is_modal_checkout_referer();
2364
+ }
2365
+
2331
2366
  /**
2332
2367
  * Is this transaction using an express checkout method?
2333
2368
  */
@@ -40,27 +40,81 @@ final class Data_Events {
40
40
  }
41
41
 
42
42
  /**
43
- * Modal Checkout Interation: Completed Order.
43
+ * Modal Checkout Interaction: Order Processed.
44
+ *
45
+ * Both hooks fire when the order is created, before payment is
46
+ * processed — not when the order reaches the "completed" status.
47
+ *
48
+ * Both WooCommerce checkout pipelines feed the same Data Events action:
49
+ * classic checkout fires woocommerce_checkout_order_processed, while
50
+ * Store API checkouts (the transport express wallets such as Apple Pay
51
+ * and Google Pay use) fire woocommerce_store_api_checkout_order_processed
52
+ * instead. Each hook gets a callback matching its exact argument shape.
44
53
  */
45
54
  \Newspack\Data_Events::register_listener(
46
55
  'woocommerce_checkout_order_processed',
47
56
  'modal_checkout_interaction',
48
57
  [ __CLASS__, 'order_status_completed' ]
49
58
  );
59
+ \Newspack\Data_Events::register_listener(
60
+ 'woocommerce_store_api_checkout_order_processed',
61
+ 'modal_checkout_interaction',
62
+ [ __CLASS__, 'store_api_order_processed' ]
63
+ );
64
+ }
65
+
66
+ /**
67
+ * Classic checkout listener callback.
68
+ *
69
+ * $posted_data and $order are accepted to match the classic hook's
70
+ * signature and are deliberately unused: the payload is built from the
71
+ * order ID alone, so both checkout pipelines share one builder.
72
+ *
73
+ * @param int $order_id Order's ID.
74
+ * @param array|null $posted_data Posted Data.
75
+ * @param \WC_Order|null $order Order object.
76
+ *
77
+ * @return array|void The event payload; void suppresses the dispatch.
78
+ */
79
+ public static function order_status_completed( $order_id, $posted_data = null, $order = null ) {
80
+ return self::get_modal_checkout_interaction_data( $order_id );
81
+ }
82
+
83
+ /**
84
+ * Store API checkout listener callback.
85
+ *
86
+ * The Store API hook passes a single \WC_Order, unlike the classic hook's
87
+ * three arguments; only a scalar order ID crosses into the shared payload
88
+ * builder, so neither callback can misread the other hook's argument list.
89
+ *
90
+ * @param mixed $order Order object. The hook contract promises a \WC_Order;
91
+ * anything else is ignored rather than trusted.
92
+ *
93
+ * @return array|void The event payload; void suppresses the dispatch.
94
+ */
95
+ public static function store_api_order_processed( $order ) {
96
+ if ( ! is_a( $order, 'WC_Order' ) ) {
97
+ return;
98
+ }
99
+
100
+ return self::get_modal_checkout_interaction_data( $order->get_id() );
50
101
  }
51
102
 
52
103
  /**
53
- * Send data to GA4.
104
+ * Build the modal checkout interaction payload for an order, or bail when
105
+ * the request is not modal-origin.
106
+ *
107
+ * Origin detection covers all three modal request shapes: the
108
+ * modal_checkout request param (classic card), express_payment_type in
109
+ * $_POST (classic express), and the modal referer carried by Store API
110
+ * JSON submissions.
111
+ *
112
+ * @param int $order_id Order's ID.
54
113
  *
55
- * @param string $order_id Order's ID.
56
- * @param array $posted_data Posted Data.
57
- * @param \WC_Order $order Order object.
114
+ * @return array|void The event payload; void suppresses the dispatch.
58
115
  */
59
- public static function order_status_completed( $order_id, $posted_data, $order ) {
60
- // Check if in a modal checkout; if no, bail.
61
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
62
- $is_modal_checkout = ( isset( $_REQUEST['modal_checkout'] ) ? true : false );
63
- if ( ! $is_modal_checkout ) {
116
+ private static function get_modal_checkout_interaction_data( $order_id ) {
117
+ if ( ! \Newspack_Blocks\Modal_Checkout::is_modal_checkout_origin() ) {
64
118
  return;
65
119
  }
66
120
 
@@ -7,7 +7,7 @@
7
7
  * Author URI: https://newspack.com/
8
8
  * Text Domain: newspack-blocks
9
9
  * Domain Path: /languages
10
- * Version: 4.30.3
10
+ * Version: 4.30.4
11
11
  *
12
12
  * @package Newspack_Blocks
13
13
  */
@@ -15,7 +15,7 @@
15
15
  define( 'NEWSPACK_BLOCKS__PLUGIN_FILE', __FILE__ );
16
16
  define( 'NEWSPACK_BLOCKS__BLOCKS_DIRECTORY', 'dist/' );
17
17
  define( 'NEWSPACK_BLOCKS__PLUGIN_DIR', plugin_dir_path( NEWSPACK_BLOCKS__PLUGIN_FILE ) );
18
- define( 'NEWSPACK_BLOCKS__VERSION', '4.30.3' );
18
+ define( 'NEWSPACK_BLOCKS__VERSION', '4.30.4' );
19
19
 
20
20
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks.php';
21
21
  require_once NEWSPACK_BLOCKS__PLUGIN_DIR . 'includes/class-newspack-blocks-api.php';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/newspack-blocks",
3
- "version": "4.30.3",
3
+ "version": "4.30.4",
4
4
  "author": "Automattic",
5
5
  "description": "=== Newspack Blocks === Contributors: (this should be a list of wordpress.org userid's) Donate link: https://example.com/ Tags: comments, spam Requires at least: 4.5 Tested up to: 5.1.1 Stable tag: 0.1.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html",
6
6
  "repository": {
@@ -3,7 +3,7 @@
3
3
  'name' => 'automattic/newspack-blocks',
4
4
  'pretty_version' => 'dev-main',
5
5
  'version' => 'dev-main',
6
- 'reference' => '6b239026e4a3dad5118443d9bc384d53248f38fa',
6
+ 'reference' => '06cd0a95946703497a8f2bba0ecfd06c69a09718',
7
7
  'type' => 'wordpress-plugin',
8
8
  'install_path' => __DIR__ . '/../../',
9
9
  'aliases' => array(),
@@ -13,7 +13,7 @@
13
13
  'automattic/newspack-blocks' => array(
14
14
  'pretty_version' => 'dev-main',
15
15
  'version' => 'dev-main',
16
- 'reference' => '6b239026e4a3dad5118443d9bc384d53248f38fa',
16
+ 'reference' => '06cd0a95946703497a8f2bba0ecfd06c69a09718',
17
17
  'type' => 'wordpress-plugin',
18
18
  'install_path' => __DIR__ . '/../../',
19
19
  'aliases' => array(),