mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-01 07:02:48 +08:00
test: Add tests for PayPal Subscriptions renewal handling
scenarios: - Preventing duplicate renewals for recent subscriptions - Creating renewal orders for subscriptions older than 8 hours - Handling subscriptions with explicit renewal meta - Ensuring payment methods are correctly transferred to renewal orders - Verifying transaction IDs are properly set on orders - Managing subscription status transitions during renewal - Handling edge cases like missing parent orders and empty subscription arrays
This commit is contained in:
parent
fe758aa0b1
commit
b342d7b226
2 changed files with 364 additions and 44 deletions
|
@ -2,79 +2,393 @@
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Tests\Integration;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Product_Simple;
|
||||
use WooCommerce\PayPalCommerce\PayPalSubscriptions\RenewalHandler;
|
||||
|
||||
/**
|
||||
* @group skip-ci
|
||||
*/
|
||||
class PayPalSubscriptionsRenewalTest extends TestCase {
|
||||
public function test_renewal_order_is_not_created_just_after_receiving_webhook() {
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler( $c->get( 'woocommerce.logger.woocommerce' ) );
|
||||
class PayPalSubscriptionsRenewalTest extends TestCase
|
||||
{
|
||||
|
||||
/**
|
||||
* Tests that renewal orders are not created for recent subscriptions.
|
||||
*
|
||||
* GIVEN a subscription created 1 minute ago
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN no renewal order should be created
|
||||
*/
|
||||
public function test_renewal_order_is_not_created_just_after_receiving_webhook()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
// Simulates receiving webhook 1 minute after subscription start.
|
||||
$subscription = $this->createSubscription( '-1 minute' );
|
||||
$subscription = $this->createSubscription('-1 minute');
|
||||
|
||||
$handler->process( [ $subscription ], 'TRANSACTION-ID' );
|
||||
$renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) );
|
||||
$this->assertEquals( count( $renewal ), 0 );
|
||||
$handler->process([$subscription], 'TRANSACTION-ID');
|
||||
$renewal = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(0, count($renewal), 'No renewal order should be created for a subscription that is only 1 minute old');
|
||||
}
|
||||
|
||||
public function test_renewal_order_is_created_when_receiving_webhook_nine_hours_later() {
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler( $c->get( 'woocommerce.logger.woocommerce' ) );
|
||||
/**
|
||||
* Tests that renewal orders are created for subscriptions older than 8 hours.
|
||||
*
|
||||
* GIVEN a subscription created 9 hours ago
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN a renewal order should be created
|
||||
*/
|
||||
public function test_renewal_order_is_created_when_receiving_webhook_nine_hours_later()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
// Simulates receiving webhook 9 hours after subscription start.
|
||||
$subscription = $this->createSubscription( '-9 hour' );
|
||||
$subscription = $this->createSubscription('-9 hour');
|
||||
|
||||
$handler->process( [ $subscription ], 'TRANSACTION-ID' );
|
||||
$renewal = $subscription->get_related_orders( 'ids', array( 'renewal' ) );
|
||||
$this->assertEquals( count( $renewal ), 1 );
|
||||
$handler->process([$subscription], 'TRANSACTION-ID');
|
||||
$renewal = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(1, count($renewal), 'A renewal order should be created for a subscription that is 9 hours old');
|
||||
}
|
||||
|
||||
private function createSubscription( string $startDate ) {
|
||||
$order = wc_create_order( [
|
||||
'customer_id' => 1,
|
||||
'set_paid' => true,
|
||||
/**
|
||||
* Tests that renewal orders are created when subscription has renewal meta.
|
||||
*
|
||||
* GIVEN a subscription created 5 minutes ago
|
||||
* AND the subscription has the _ppcp_is_subscription_renewal meta set to 'true'
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN a renewal order should be created
|
||||
*/
|
||||
public function test_renewal_order_is_created_when_subscription_has_renewal_meta()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
// Create a subscription that's only 5 minutes old (would normally not trigger renewal)
|
||||
$subscription = $this->createSubscription('-5 minute');
|
||||
|
||||
// But mark it as needing renewal
|
||||
$subscription->update_meta_data('_ppcp_is_subscription_renewal', 'true');
|
||||
$subscription->save_meta_data();
|
||||
|
||||
$handler->process([$subscription], 'TRANSACTION-ID');
|
||||
$renewal = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(1, count($renewal), 'A renewal order should be created when subscription has _ppcp_is_subscription_renewal meta set to true, regardless of age');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that renewal order payment method matches the subscription.
|
||||
*
|
||||
* GIVEN a subscription created 9 hours ago
|
||||
* AND the subscription has a specific payment method
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN a renewal order should be created
|
||||
* AND the renewal order should have the same payment method as the subscription
|
||||
*/
|
||||
public function test_renewal_order_payment_method_matches_subscription()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
$subscription = $this->createSubscription('-9 hour');
|
||||
$payment_method = 'ppcp-gateway';
|
||||
$subscription->set_payment_method($payment_method);
|
||||
$subscription->save();
|
||||
|
||||
$handler->process([$subscription], 'TRANSACTION-ID');
|
||||
$renewal_ids = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(1, count($renewal_ids), 'A renewal order should be created for a subscription that is 9 hours old');
|
||||
|
||||
$renewal_order = wc_get_order(reset($renewal_ids));
|
||||
$this->assertEquals($payment_method, $renewal_order->get_payment_method(), 'The renewal order should have the same payment method as the subscription');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that renewal orders are marked as paid.
|
||||
*
|
||||
* GIVEN a subscription created 9 hours ago
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN a renewal order should be created
|
||||
* AND the renewal order should be marked as paid
|
||||
*/
|
||||
public function test_renewal_order_is_marked_as_paid()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
$subscription = $this->createSubscription('-9 hour');
|
||||
|
||||
$handler->process([$subscription], 'TRANSACTION-ID');
|
||||
$renewal_ids = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(1, count($renewal_ids), 'A renewal order should be created for a subscription that is 9 hours old');
|
||||
|
||||
$renewal_order = wc_get_order(reset($renewal_ids));
|
||||
$this->assertTrue($renewal_order->is_paid(), 'The renewal order should be marked as paid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that transaction ID is set on renewal orders.
|
||||
*
|
||||
* GIVEN a subscription created 9 hours ago
|
||||
* AND a unique transaction ID
|
||||
* WHEN the process method is called with this subscription and transaction ID
|
||||
* THEN a renewal order should be created
|
||||
* AND the renewal order should have the transaction ID set
|
||||
*/
|
||||
public function test_transaction_id_is_set_on_renewal_order()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
$subscription = $this->createSubscription('-9 hour');
|
||||
$transaction_id = 'TEST-TRANSACTION-ID-' . uniqid();
|
||||
|
||||
$handler->process([$subscription], $transaction_id);
|
||||
$renewal_ids = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(1, count($renewal_ids), 'A renewal order should be created for a subscription that is 9 hours old');
|
||||
|
||||
$renewal_order = wc_get_order(reset($renewal_ids));
|
||||
$this->assertEquals($transaction_id, $renewal_order->get_transaction_id(), 'The renewal order should have the transaction ID set correctly');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that subscription status is set to on-hold before renewal.
|
||||
*
|
||||
* GIVEN a subscription created 9 hours ago with 'active' status
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN the subscription status should be changed to 'on-hold'
|
||||
*/
|
||||
public function test_subscription_status_is_set_to_on_hold_before_renewal()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
$subscription = $this->createSubscription('-9 hour');
|
||||
$initial_status = $subscription->get_status();
|
||||
$this->assertEquals('active', $initial_status, 'The subscription should start with active status');
|
||||
|
||||
$handler->process([$subscription], 'TRANSACTION-ID');
|
||||
|
||||
// Status should be on-hold before the renewal order is created
|
||||
$this->assertEquals('on-hold', $subscription->get_status(), 'The subscription status should be changed to on-hold before renewal');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that transaction ID is set on parent order when no renewal is created.
|
||||
*
|
||||
* GIVEN a subscription created 1 minute ago
|
||||
* AND a unique transaction ID
|
||||
* WHEN the process method is called with this subscription and transaction ID
|
||||
* THEN no renewal order should be created
|
||||
* AND the transaction ID should be set on the parent order
|
||||
*/
|
||||
public function test_transaction_id_is_set_on_parent_order_when_no_renewal()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
$subscription = $this->createSubscription('-1 minute');
|
||||
|
||||
$transaction_id = 'PARENT-TRANSACTION-ID-' . uniqid();
|
||||
$parent_order_id = $subscription->get_parent_id();
|
||||
|
||||
$handler->process([$subscription], $transaction_id);
|
||||
|
||||
// No renewal order should be created
|
||||
$renewal = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(0, count($renewal), 'No renewal order should be created for a subscription that is only 1 minute old');
|
||||
|
||||
//use latest order to get the updated status
|
||||
$parent_order = wc_get_order($parent_order_id);
|
||||
// Transaction ID should be set on parent order
|
||||
$this->assertEquals($transaction_id, $parent_order->get_transaction_id(), 'The transaction ID should be set on the parent order when no renewal is created');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that subscription meta is set when processing parent order.
|
||||
*
|
||||
* GIVEN a subscription created 1 minute ago
|
||||
* AND the subscription has no _ppcp_is_subscription_renewal meta
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN the _ppcp_is_subscription_renewal meta should be set to 'true'
|
||||
*/
|
||||
public function test_subscription_meta_is_set_when_processing_parent_order()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
$subscription = $this->createSubscription('-1 minute');
|
||||
|
||||
// Meta should not exist before processing
|
||||
$this->assertEmpty($subscription->get_meta('_ppcp_is_subscription_renewal'), 'The subscription should not have _ppcp_is_subscription_renewal meta before processing');
|
||||
|
||||
$handler->process([$subscription], 'TRANSACTION-ID');
|
||||
|
||||
// Meta should be set after processing
|
||||
$this->assertEquals('true', $subscription->get_meta('_ppcp_is_subscription_renewal'), 'The _ppcp_is_subscription_renewal meta should be set to true after processing');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests handling subscriptions without valid parent orders.
|
||||
*
|
||||
* GIVEN a subscription created 9 hours ago
|
||||
* AND the parent order is not available
|
||||
* WHEN the process method is called with this subscription
|
||||
* THEN a renewal order should still be created
|
||||
* AND the renewal order should be properly set up with transaction ID
|
||||
* AND the subscription status should be set to 'on-hold'
|
||||
*/
|
||||
public function test_subscription_without_valid_parent_order()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$handler = new RenewalHandler($c->get('woocommerce.logger.woocommerce'));
|
||||
|
||||
$subscription = $this->createSubscription('-9 hour');
|
||||
$transaction_id = 'TEST-TRANSACTION-ID-' . uniqid();
|
||||
|
||||
// Simulate a scenario where the parent order doesn't exist or is not a WC_Order
|
||||
// Mock wc_get_order to return false instead of a WC_Order instance
|
||||
add_filter('woocommerce_get_shop_order_args', function ($args) use ($subscription) {
|
||||
if (isset($args['id']) && $args['id'] === $subscription->get_parent_id()) {
|
||||
return ['return' => false]; // This causes wc_get_order to return false
|
||||
}
|
||||
return $args;
|
||||
});
|
||||
|
||||
// Process should not throw any errors
|
||||
$handler->process([$subscription], $transaction_id);
|
||||
|
||||
// Verify that a renewal order was created (as the subscription is 9 hours old)
|
||||
$renewal_ids = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(1, count($renewal_ids), 'A renewal order should be created even when the parent order is not available');
|
||||
|
||||
// Verify the renewal order was properly set up
|
||||
$renewal_order = wc_get_order(reset($renewal_ids));
|
||||
$this->assertTrue($renewal_order->is_paid(), 'The renewal order should be marked as paid even when the parent order is not available');
|
||||
$this->assertEquals($transaction_id, $renewal_order->get_transaction_id(), 'The renewal order should have the transaction ID set correctly even when the parent order is not available');
|
||||
|
||||
// Verify no errors occurred due to invalid parent order
|
||||
$this->assertEquals('on-hold', $subscription->get_status(), 'The subscription status should be set to on-hold even when the parent order is not available');
|
||||
|
||||
// Remove the filter
|
||||
remove_all_filters('woocommerce_get_shop_order_args');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tests that parent order transaction ID is updated for non-renewal subscriptions.
|
||||
*
|
||||
* GIVEN a subscription created 1 minute ago
|
||||
* AND the parent order has no transaction ID
|
||||
* WHEN the process method is called with this subscription and a unique transaction ID
|
||||
* THEN the parent order's transaction ID should be updated
|
||||
* AND the subscription should be marked for future renewal
|
||||
* AND no renewal order should be created
|
||||
*/
|
||||
public function test_parent_order_transaction_id_is_updated_when_processing_non_renewal_subscription()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$logger = $c->get('woocommerce.logger.woocommerce');
|
||||
$handler = new RenewalHandler($logger);
|
||||
|
||||
// Create a subscription that's not ready for renewal
|
||||
$subscription = $this->createSubscription('-1 minute');
|
||||
|
||||
// Get the parent order
|
||||
$parent_order_id = $subscription->get_parent_id();
|
||||
$parent_order = wc_get_order($parent_order_id);
|
||||
|
||||
$this->assertEmpty($parent_order->get_transaction_id(), 'The parent order should not have a transaction ID before processing');
|
||||
|
||||
$transaction_id = 'PARENT-ORDER-TRANSACTION-' . uniqid();
|
||||
$handler->process([$subscription], $transaction_id);
|
||||
$parent_order = wc_get_order($parent_order_id);
|
||||
|
||||
$this->assertEquals($transaction_id, $parent_order->get_transaction_id(), 'The parent order transaction ID should be updated correctly');
|
||||
|
||||
$this->assertEquals('true', $subscription->get_meta('_ppcp_is_subscription_renewal'), 'The subscription should be marked for future renewal after processing');
|
||||
|
||||
$renewal_orders = $subscription->get_related_orders('ids', array('renewal'));
|
||||
$this->assertEquals(0, count($renewal_orders), 'No renewal order should be created for an empty array of subscriptions');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the RenewalHandler correctly handles an empty array of subscriptions.
|
||||
*
|
||||
* GIVEN the RenewalHandler with a mocked logger
|
||||
* WHEN the process method is called with an empty array of subscriptions
|
||||
* THEN no exceptions should be thrown
|
||||
* AND the logger should not be called
|
||||
*/
|
||||
public function test_process_empty_subscriptions_array()
|
||||
{
|
||||
$c = $this->getContainer();
|
||||
$logger = $c->get('woocommerce.logger.woocommerce');
|
||||
|
||||
// Create a logger mock that expects no operations if no subscriptions
|
||||
$logger_mock = \Mockery::mock(LoggerInterface::class);
|
||||
// The logger should not be called at all with an empty array
|
||||
$logger_mock->shouldNotReceive('info');
|
||||
|
||||
$handler = new RenewalHandler($logger_mock);
|
||||
$transaction_id = 'TEST-TRANSACTION-EMPTY-ARRAY';
|
||||
|
||||
// Process an empty array of subscriptions
|
||||
$handler->process([], $transaction_id);
|
||||
|
||||
// Test is successful if no exceptions are thrown
|
||||
// and the mock expectations are met (logger not called)
|
||||
$this->assertTrue(true, 'No exceptions were thrown when processing an empty array of subscriptions');
|
||||
}
|
||||
|
||||
private function createSubscription(string $startDate)
|
||||
{
|
||||
$order = wc_create_order([
|
||||
'customer_id' => 1,
|
||||
'set_paid' => true,
|
||||
'payment_method' => 'ppcp-gateway',
|
||||
'billing' => [
|
||||
'billing' => [
|
||||
'first_name' => 'John',
|
||||
'last_name' => 'Doe',
|
||||
'address_1' => '969 Market',
|
||||
'address_2' => '',
|
||||
'city' => 'San Francisco',
|
||||
'state' => 'CA',
|
||||
'postcode' => '94103',
|
||||
'country' => 'US',
|
||||
'email' => 'john.doe@example.com',
|
||||
'phone' => '(555) 555-5555'
|
||||
'last_name' => 'Doe',
|
||||
'address_1' => '969 Market',
|
||||
'address_2' => '',
|
||||
'city' => 'San Francisco',
|
||||
'state' => 'CA',
|
||||
'postcode' => '94103',
|
||||
'country' => 'US',
|
||||
'email' => 'john.doe@example.com',
|
||||
'phone' => '(555) 555-5555'
|
||||
],
|
||||
'line_items' => [
|
||||
'line_items' => [
|
||||
[
|
||||
'product_id' => 42,
|
||||
'quantity' => 1
|
||||
'quantity' => 1
|
||||
]
|
||||
],
|
||||
] );
|
||||
]);
|
||||
$order->set_status('completed');
|
||||
// Make sure the order is properly saved
|
||||
$order->save();
|
||||
|
||||
$product = new WC_Product_Simple();
|
||||
$product->set_props([
|
||||
'name' => 'Dummy Product',
|
||||
'name' => 'Dummy Product',
|
||||
'regular_price' => 10,
|
||||
'price' => 10,
|
||||
'sku' => 'DUMMY SKU',
|
||||
'manage_stock' => false,
|
||||
'tax_status' => 'taxable',
|
||||
'downloadable' => false,
|
||||
'virtual' => false,
|
||||
'stock_status' => 'instock',
|
||||
'weight' => '1.1',
|
||||
'price' => 10,
|
||||
'sku' => 'DUMMY SKU',
|
||||
'manage_stock' => false,
|
||||
'tax_status' => 'taxable',
|
||||
'downloadable' => false,
|
||||
'virtual' => false,
|
||||
'stock_status' => 'instock',
|
||||
'weight' => '1.1',
|
||||
]);
|
||||
$product->save();
|
||||
|
||||
return wcs_create_subscription([
|
||||
'start_date' => gmdate( 'Y-m-d H:i:s', strtotime($startDate) ),
|
||||
'parent_id' => $order->get_id(),
|
||||
$subscription = wcs_create_subscription([
|
||||
'start_date' => gmdate('Y-m-d H:i:s', strtotime($startDate)),
|
||||
'order_id' => $order->get_id(),
|
||||
'customer_id' => 1,
|
||||
'status' => 'active',
|
||||
'billing_period' => 'day',
|
||||
|
@ -87,5 +401,9 @@ class PayPalSubscriptionsRenewalTest extends TestCase {
|
|||
]
|
||||
],
|
||||
]);
|
||||
|
||||
// Make sure the subscription is properly saved
|
||||
$subscription->save();
|
||||
return $subscription;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,3 +21,5 @@ define('WP_ROOT_DIR', $wpRootDir);
|
|||
$_SERVER['HTTP_HOST'] = ''; // just to avoid a warning
|
||||
|
||||
require_once WP_ROOT_DIR . '/wp-load.php';
|
||||
// Ensure the TestCase class is loaded
|
||||
require_once __DIR__ . '/TestCase.php';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue