@@ -240,4 +240,133 @@ public function test_valid_order_session_proceeds_to_replace_order(): void
240240
241241 // Then — replace_order called with the correct order and success response sent
242242 }
243+
244+ /**
245+ * Creates a SUT instance with final_review_enabled set to true (Pay Now disabled).
246+ */
247+ private function create_sut_with_final_review ( bool $ final_review_enabled ): ApproveOrderEndpoint
248+ {
249+ return new ApproveOrderEndpoint (
250+ $ this ->request_data ,
251+ $ this ->api_endpoint ,
252+ $ this ->session_handler ,
253+ $ this ->threed_secure ,
254+ $ this ->settings_provider ,
255+ $ this ->settings_model ,
256+ $ this ->dcc_applies ,
257+ $ this ->order_helper ,
258+ $ final_review_enabled ,
259+ $ this ->gateway ,
260+ $ this ->wc_order_creator ,
261+ $ this ->logger ,
262+ $ this ->context
263+ );
264+ }
265+
266+ /**
267+ * Sets up common mocks for the WC order creation flow tests.
268+ *
269+ * @return Order&\Mockery\MockInterface
270+ */
271+ private function arrange_order_creation_flow ( string $ order_id , string $ funding_source , bool $ should_create_wc_order ): Order
272+ {
273+ $ session_id = 'test-session ' ;
274+ $ purchase_unit = Mockery::mock ( PurchaseUnit::class );
275+ $ purchase_unit ->shouldReceive ( 'custom_id ' )->andReturn ( 'pcp_customer_ ' . $ session_id );
276+
277+ $ order_status = Mockery::mock ( OrderStatus::class );
278+ $ order_status ->shouldReceive ( 'is ' )->andReturn ( true );
279+
280+ $ order = Mockery::mock ( Order::class );
281+ $ order ->shouldReceive ( 'purchase_units ' )->andReturn ( array ( $ purchase_unit ) );
282+ $ order ->shouldReceive ( 'payment_source ' )->andReturn ( null );
283+ $ order ->shouldReceive ( 'status ' )->andReturn ( $ order_status );
284+
285+ $ this ->request_data ->shouldReceive ( 'read_request ' )
286+ ->with ( ApproveOrderEndpoint::nonce () )
287+ ->andReturn ( array (
288+ 'order_id ' => $ order_id ,
289+ 'funding_source ' => $ funding_source ,
290+ 'should_create_wc_order ' => $ should_create_wc_order ,
291+ ) );
292+ $ this ->api_endpoint ->shouldReceive ( 'order ' )->with ( $ order_id )->andReturn ( $ order );
293+ $ this ->session_handler ->shouldReceive ( 'replace_funding_source ' )->once ();
294+ $ this ->session_handler ->shouldReceive ( 'replace_order ' )->once ();
295+ $ this ->context ->shouldReceive ( 'is_checkout ' )->andReturn ( false );
296+
297+ $ wc_session = Mockery::mock ( \WC_Session_Handler::class );
298+ $ wc_session ->shouldReceive ( 'get_customer_unique_id ' )->andReturn ( $ session_id );
299+ $ wc_session ->shouldReceive ( 'set ' );
300+ $ wc_cart = Mockery::mock ( \WC_Cart::class );
301+ $ wc = Mockery::mock ();
302+ $ wc ->session = $ wc_session ;
303+ $ wc ->cart = $ wc_cart ;
304+ when ( 'WC ' )->justReturn ( $ wc );
305+
306+ return $ order ;
307+ }
308+
309+ /**
310+ * @scenario When final_review_enabled=true (Pay Now disabled) and funding_source is express checkout,
311+ * the express checkout bypass should create a WC order and process payment.
312+ *
313+ * @dataProvider express_checkout_funding_sources
314+ */
315+ public function test_express_checkout_creates_wc_order_even_with_final_review_enabled ( string $ funding_source ): void
316+ {
317+ // Arrange
318+ $ sut = $ this ->create_sut_with_final_review ( true );
319+ $ order = $ this ->arrange_order_creation_flow ( 'EXPRESS-ORDER ' , $ funding_source , true );
320+
321+ $ wc_order = Mockery::mock ( \WC_Order::class );
322+ $ wc_order ->shouldReceive ( 'get_id ' )->andReturn ( 42 );
323+ $ wc_order ->shouldReceive ( 'get_checkout_order_received_url ' )->andReturn ( 'https://example.com/order-received/42 ' );
324+
325+ $ this ->wc_order_creator ->shouldReceive ( 'create_from_paypal_order ' )->once ()->andReturn ( $ wc_order );
326+ $ this ->gateway ->shouldReceive ( 'process_payment ' )->once ()->with ( 42 );
327+
328+ expect ( 'wp_send_json_success ' )->once ()->with (
329+ Mockery::on ( static function ( $ data ) {
330+ return isset ( $ data ['order_received_url ' ] );
331+ } )
332+ );
333+
334+ // When
335+ $ sut ->handle_request ();
336+
337+ // Then — WC order created and payment processed despite final_review_enabled=true
338+ }
339+
340+ /**
341+ * Data provider for express checkout funding sources.
342+ */
343+ public static function express_checkout_funding_sources (): array
344+ {
345+ return array (
346+ 'Apple Pay ' => array ( 'apple_pay ' ),
347+ 'Google Pay ' => array ( 'googlepay ' ),
348+ );
349+ }
350+
351+ /**
352+ * @scenario When final_review_enabled=true and funding_source is 'paypal' (standard flow),
353+ * no WC order should be created — the user must complete checkout manually.
354+ */
355+ public function test_standard_paypal_does_not_create_wc_order_when_final_review_enabled (): void
356+ {
357+ // Arrange
358+ $ sut = $ this ->create_sut_with_final_review ( true );
359+ $ this ->arrange_order_creation_flow ( 'PAYPAL-ORDER ' , 'paypal ' , true );
360+
361+ $ this ->wc_order_creator ->shouldReceive ( 'create_from_paypal_order ' )->never ();
362+ $ this ->gateway ->shouldReceive ( 'process_payment ' )->never ();
363+
364+ expect ( 'wp_send_json_success ' )->once ();
365+
366+ // When
367+ $ sut ->handle_request ();
368+
369+ // Then — no WC order creation, standard continuation flow
370+ }
371+
243372}
0 commit comments