mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2026-07-31 02:32:53 +08:00
201 lines
7.4 KiB
PHP
201 lines
7.4 KiB
PHP
<?php
|
|
declare( strict_types=1 );
|
|
|
|
namespace WooCommerce\PayPalCommerce\Tests\Integration\Button\Endpoint;
|
|
|
|
use Mockery;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
|
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
|
|
|
/**
|
|
* Contract tests for the ppc-create-order WC-AJAX endpoint, shared by the
|
|
* v5 and v6 SDK frontends.
|
|
*
|
|
* @covers \WooCommerce\PayPalCommerce\OrderEndpoints\Endpoint\CreateOrderEndpoint
|
|
*/
|
|
class CreateOrderEndpointContractTest extends WcAjaxEndpointTestCase {
|
|
|
|
private const PAYPAL_ORDER_ID = 'TEST-PAYPAL-ORDER-123';
|
|
|
|
/**
|
|
* Arguments captured from the mocked OrderEndpoint::create() call.
|
|
*
|
|
* @var array|null
|
|
*/
|
|
private $captured_create;
|
|
|
|
/**
|
|
* Boots the container with a mocked PayPal order API whose create() returns
|
|
* a real Order wrapping the purchase units the endpoint built from the live
|
|
* cart, and captures the call arguments.
|
|
*
|
|
* @return object The ppc-create-order endpoint.
|
|
*/
|
|
private function endpointWithMockedCreate() {
|
|
$this->captured_create = null;
|
|
$captured = &$this->captured_create;
|
|
|
|
$order_endpoint = Mockery::mock( OrderEndpoint::class );
|
|
$order_endpoint->shouldReceive( 'with_bn_code' )->andReturnSelf()->byDefault();
|
|
$order_endpoint->shouldReceive( 'create' )->andReturnUsing(
|
|
function ( array $purchase_units, string $shipping_preference ) use ( &$captured ): Order {
|
|
$captured = array(
|
|
'purchase_units' => $purchase_units,
|
|
'shipping_preference' => $shipping_preference,
|
|
);
|
|
|
|
return new Order(
|
|
self::PAYPAL_ORDER_ID,
|
|
$purchase_units,
|
|
new OrderStatus( OrderStatus::CREATED )
|
|
);
|
|
}
|
|
);
|
|
|
|
$container = $this->bootstrapWithOrderApi( $order_endpoint );
|
|
|
|
return $container->get( 'button.endpoint.create-order' );
|
|
}
|
|
|
|
/**
|
|
* The non-checkout contexts sharing the same create-order contract.
|
|
*
|
|
* @return array<string, array{0: string}>
|
|
*/
|
|
public function nonCheckoutContextProvider(): array {
|
|
return array(
|
|
'cart context' => array( 'cart' ),
|
|
'product context' => array( 'product' ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* GIVEN a populated WC cart (virtual product, price 10, quantity 2)
|
|
* WHEN ppc-create-order is called with a valid nonce and context: cart or product
|
|
* THEN the response contains the PayPal order id in data.id and a custom_id key
|
|
* AND the order is created from exactly one purchase unit reflecting the cart total
|
|
* AND the purchase unit custom_id is blanked (non-checkout contexts must not
|
|
* allow webhook completion binding)
|
|
* AND no PayPal order is stored in the plugin session
|
|
*
|
|
* The product context additionally requires purchase_units[0].items[0].url
|
|
* in the request body (the product page URL, consumed by ReturnUrlFactory
|
|
* as the PayPal return URL).
|
|
*
|
|
* @dataProvider nonCheckoutContextProvider
|
|
*
|
|
* @param string $context The request context.
|
|
*/
|
|
public function test_non_checkout_context_returns_paypal_order_id( string $context ): void {
|
|
$product = $this->addVirtualProductToCart( 10.0, 2 );
|
|
$endpoint = $this->endpointWithMockedCreate();
|
|
|
|
$body = array(
|
|
'context' => $context,
|
|
'payment_method' => 'ppcp-gateway',
|
|
'funding_source' => 'paypal',
|
|
);
|
|
if ( 'product' === $context ) {
|
|
$body['purchase_units'] = array(
|
|
array(
|
|
'items' => array(
|
|
array( 'url' => get_permalink( $product->get_id() ) ),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
$response = $this->dispatchAjaxRequest( $endpoint, $body );
|
|
|
|
$this->assertTrue( $response['success'], 'ppc-create-order must succeed: ' . $response['raw'] );
|
|
$this->assertSame( self::PAYPAL_ORDER_ID, $response['data']['id'] ?? null, 'data.id must be the PayPal order id' );
|
|
$this->assertArrayHasKey( 'custom_id', $response['data'] );
|
|
|
|
$this->assertNotNull( $this->captured_create, 'The PayPal create order API must be called' );
|
|
$this->assertCount( 1, $this->captured_create['purchase_units'] );
|
|
|
|
$purchase_unit = $this->captured_create['purchase_units'][0];
|
|
$this->assertInstanceOf( PurchaseUnit::class, $purchase_unit );
|
|
$this->assertSame( '20.00', $purchase_unit->to_array()['amount']['value'], 'Amount must equal the cart total' );
|
|
$this->assertSame( '', $purchase_unit->custom_id(), ucfirst( $context ) . ' context must blank the purchase unit custom_id' );
|
|
$this->assertNotSame( '', $this->captured_create['shipping_preference'] );
|
|
|
|
$session_handler = $this->getContainer()->get( 'session.handler' );
|
|
$this->assertNull( $session_handler->order(), ucfirst( $context ) . ' context must not store a session order' );
|
|
}
|
|
|
|
/**
|
|
* GIVEN a populated WC cart
|
|
* WHEN ppc-create-order creates the PayPal order
|
|
* THEN the woocommerce_paypal_payments_create_order_endpoint_order_created
|
|
* action fires once with the created Order entity and the request data
|
|
* (the hook the v6 SDK module consumes to store session orders for
|
|
* non-checkout contexts)
|
|
*/
|
|
public function test_create_order_endpoint_fires_order_created_action(): void {
|
|
$this->addVirtualProductToCart( 10.0 );
|
|
$endpoint = $this->endpointWithMockedCreate();
|
|
|
|
$calls = $this->recordAction( 'woocommerce_paypal_payments_create_order_endpoint_order_created', 2 );
|
|
|
|
$response = $this->dispatchAjaxRequest(
|
|
$endpoint,
|
|
array(
|
|
'context' => 'cart',
|
|
'payment_method' => 'ppcp-gateway',
|
|
'funding_source' => 'paypal',
|
|
)
|
|
);
|
|
|
|
$this->assertTrue( $response['success'], 'ppc-create-order must succeed: ' . $response['raw'] );
|
|
$this->assertCount( 1, $calls, 'The order_created action must fire exactly once' );
|
|
|
|
list( $order, $data ) = $calls[0];
|
|
$this->assertInstanceOf( Order::class, $order );
|
|
$this->assertSame( self::PAYPAL_ORDER_ID, $order->id() );
|
|
$this->assertIsArray( $data );
|
|
$this->assertSame( 'cart', $data['context'] ?? null, 'The action must receive the sanitized request data' );
|
|
}
|
|
|
|
/**
|
|
* GIVEN a populated WC cart
|
|
* WHEN ppc-create-order is called with context: checkout, the PayPal gateway
|
|
* and no redirect-less funding source
|
|
* THEN the created PayPal order is stored in the plugin session ('ppcp' key)
|
|
* AND the purchase unit keeps the session-bound custom_id
|
|
* (pcp_customer_ + WC session customer id) used by the
|
|
* approve-order ownership check
|
|
*/
|
|
public function test_checkout_context_stores_order_in_session(): void {
|
|
$this->addVirtualProductToCart( 10.0 );
|
|
$endpoint = $this->endpointWithMockedCreate();
|
|
|
|
$response = $this->dispatchAjaxRequest(
|
|
$endpoint,
|
|
array(
|
|
'context' => 'checkout',
|
|
'payment_method' => 'ppcp-gateway',
|
|
)
|
|
);
|
|
|
|
$this->assertTrue( $response['success'], 'ppc-create-order must succeed: ' . $response['raw'] );
|
|
$this->assertSame( self::PAYPAL_ORDER_ID, $response['data']['id'] ?? null );
|
|
|
|
$session_handler = $this->getContainer()->get( 'session.handler' );
|
|
$session_order = $session_handler->order();
|
|
$this->assertNotNull( $session_order, 'Checkout context must store the PayPal order in the session' );
|
|
$this->assertSame( self::PAYPAL_ORDER_ID, $session_order->id() );
|
|
|
|
$stored = WC()->session->get( 'ppcp' );
|
|
$this->assertInstanceOf( Order::class, $stored['order'] ?? null, 'The session order must be stored as an Order entity under the ppcp session key' );
|
|
|
|
$purchase_unit = $this->captured_create['purchase_units'][0];
|
|
$this->assertSame(
|
|
$this->sessionCustomId(),
|
|
$purchase_unit->custom_id(),
|
|
'Checkout context must keep the session-bound custom_id on the purchase unit'
|
|
);
|
|
}
|
|
}
|