recurly-8.x-1.x-dev/tests/src/Functional/SubscriptionCancelTest.php

tests/src/Functional/SubscriptionCancelTest.php
<?php

namespace Drupal\Tests\recurly\Functional;

use Drupal\recurly_test_client\RecurlyMockClient;

/**
 * Tests ability to cancel a recurly subscription.
 *
 * @covers \Drupal\recurly\Controller\RecurlySubscriptionCancelController
 * @covers \Drupal\recurly\Form\RecurlySubscriptionCancelConfirmForm
 * @group recurly
 */
class SubscriptionCancelTest extends RecurlyBrowserTestBase {

  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();
    $this->drupalPlaceBlock('local_tasks_block');
    $this->drupalPlaceBlock('system_messages_block');
  }

  /**
   * Helper method to add mock responses shared by most test methods.
   */
  protected function addSharedResponses() {
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890', 'accounts/show-200.xml');
    RecurlyMockClient::addResponse('HEAD', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', 'subscriptions/head-200-single.xml');
    $fixture_index_200_single = [
      'fixture' => 'subscriptions/index-200-single.xml',
      'alterations' => [
        '//current_period_started_at' => date('c', strtotime('-2 weeks')),
        '//current_period_ends_at' => date('c', strtotime('+2 weeks')),
      ],
    ];
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', $fixture_index_200_single);
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=200', $fixture_index_200_single);
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?state=past_due', 'subscriptions/empty-200.xml');
    $fixture_show_200 = $fixture_index_200_single;
    $fixture_show_200['fixture'] = 'subscriptions/show-200.xml';
    RecurlyMockClient::addResponse('GET', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4', $fixture_show_200);
  }

  /**
   * Test that a bad subscription ID returns a 404.
   */
  public function testUserCancelSubscriptionNotFound() {
    RecurlyMockClient::clear();
    RecurlyMockClient::addResponse('GET', '/subscriptions/non-existent-subscription', 'subscriptions/missing-404.xml');

    $account = $this->createUserWithSubscription();
    $this->drupalLogin($account);
    $this->drupalGet('/user/' . $account->id() . '/subscription/id/non-existent-subscription/cancel');
    $this->assertSession()->statusCodeEquals(404);
    $this->assertSession()->pageTextContains('Subscription not found ');
  }

  /**
   * Test user's ability to cancel their subscription at next renewal.
   */
  public function testUserCancelAtRenewal() {
    RecurlyMockClient::clear();
    $this->addSharedResponses();

    $this->config('recurly.settings')
      ->set('recurly_subscription_cancel_behavior', 'cancel')
      ->save();

    $account = $this->createUserWithSubscription();
    $this->drupalLogin($account);

    // Routes to 'user/' . $account->id() . '/subscription/id/latest/cancel'.
    // Test that the dynamic route which automatically chooses the latest
    // subscription works.
    $this->drupalGet($account->toUrl('recurly-cancellatest'));
    $this->assertSession()->pageTextContains('Cancel subscription');
    $this->assertSession()->buttonExists('Cancel at Renewal');

    // Load the form again, this by do so by navigating there like a user would.
    // This also confirms the UUID version of the URL works.
    $this->drupalGet($account->toUrl());
    $this->clickLink('Subscription');
    $this->assertSession()->pageTextContains('Subscription Summary');
    $this->clickLink('Cancel');
    $this->assertSession()->addressMatches('@^/user/' . $account->id() . '/subscription/id/.*/cancel$@');
    $this->assertSession()->pageTextContains('Cancel subscription');
    $this->assertSession()->buttonExists('Cancel at Renewal');

    // When the form is submitted it'll make a PUT request to Recurly to update
    // the subscription. We need to mock a response for that, and update the
    // subscription GET response to indicate that it's been cancelled in the
    // Recurly backend.
    RecurlyMockClient::addResponse('PUT', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4/cancel', 'subscriptions/cancel-200.xml');
    $fixture_index_200_single = [
      'fixture' => 'subscriptions/index-200-single.xml',
      'alterations' => [
        '//current_period_started_at' => date('c', strtotime('-2 weeks')),
        '//current_period_ends_at' => date('c', strtotime('+2 weeks')),
        '//state' => 'canceled',
        '//updated_at' => date('c'),
        '//canceled_at' => date('c'),
        '//expires_at' => date('c', strtotime('+2 weeks')),
      ],
    ];
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', $fixture_index_200_single);

    $this->getSession()->getPage()->pressButton('Cancel at Renewal');
    $this->assertSession()->pageTextContains('Plan Silver Plan canceled!');
    $this->assertSession()->elementTextContains('css', '.subscription .status', 'Canceled (will not renew)');
    $this->assertTrue(RecurlyMockClient::assertRequestMade('PUT', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4/cancel'));
  }

  /**
   * Test ability to terminate subscription immediately with partial refund.
   */
  public function testUserCancelTerminateProratedRefund() {
    RecurlyMockClient::clear();
    $this->addSharedResponses();

    $this->config('recurly.settings')
      ->set('recurly_subscription_cancel_behavior', 'terminate_prorated')
      ->save();

    $account = $this->createUserWithSubscription();
    $this->drupalLogin($account);

    $recurly_account = recurly_account_load([
      'entity_type' => $account->bundle(),
      'entity_id' => $account->id(),
    ], TRUE);
    $subscriptions = recurly_account_get_subscriptions($recurly_account->account_code, 'active');
    /** @var \Recurly_Subscription $subscription */
    $subscription = reset($subscriptions);

    // Load the page directly. We tested the navigation elsewhere.
    $this->drupalGet('/user/' . $account->id() . '/subscription/id/' . $subscription->uuid . '/cancel');
    $this->assertSession()->pageTextContains('Cancel subscription');

    // $unit_amount_in_cents * $remaining_time / $total_period_time
    // See recurly_subscription_calculate_refund()
    $this->assertSession()->pageTextMatches('/A refund of\s\$.*\sUSD will be credited to your account/');
    $this->assertSession()->buttonExists('Cancel Plan');

    // When the cancel form is submitted it'll trigger a PUT call to Recurly
    // which we need to mock a response for. As well as change the response
    // that is returned when retrieving the subscription so that it now shows
    // that it's been terminated. This mocks logic that happens in Recurly as
    // a result of the PUT request.
    RecurlyMockClient::addResponse('PUT', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4/terminate?refund=partial&charge=true', 'subscriptions/cancel-200.xml');
    // After this the subscription response should be canceled.
    $fixture_index_200_single = [
      'fixture' => 'subscriptions/index-200-single.xml',
      'alterations' => [
        '//current_period_started_at' => date('c', strtotime('-2 weeks')),
        '//current_period_ends_at' => date('c', strtotime('+2 weeks')),
        '//state' => 'expired',
        '//updated_at' => date('c'),
        '//canceled_at' => date('c'),
        '//expires_at' => date('c'),
      ],
    ];
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', $fixture_index_200_single);

    // Submit the form. And verify the updated summary page.
    $this->getSession()->getPage()->pressButton('Cancel Plan');
    $this->assertSession()->pageTextContains('Plan Silver Plan terminated!');
    $this->assertSession()->elementTextContains('css', '.subscription .status', 'Expired');
    // Also verify that a PUT request gets made to Recurly.
    $this->assertTrue(RecurlyMockClient::assertRequestMade('PUT', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4/terminate?refund=partial&charge=true'));
  }

  /**
   * Test ability to terminate subscription immediately with full refund.
   */
  public function testUserCancelTerminateFullRefund() {
    RecurlyMockClient::clear();
    $this->addSharedResponses();

    $this->config('recurly.settings')
      ->set('recurly_subscription_cancel_behavior', 'terminate_full')
      ->save();

    $account = $this->createUserWithSubscription();
    $this->drupalLogin($account);

    $recurly_account = recurly_account_load([
      'entity_type' => $account->bundle(),
      'entity_id' => $account->id(),
    ], TRUE);
    $subscriptions = recurly_account_get_subscriptions($recurly_account->account_code, 'active');
    /** @var \Recurly_Subscription $subscription */
    $subscription = reset($subscriptions);

    // Load the page directly. We tested the navigation elsewhere.
    $this->drupalGet('/user/' . $account->id() . '/subscription/id/' . $subscription->uuid . '/cancel');
    $this->assertSession()->pageTextContains('Cancel subscription');

    // See recurly_subscription_calculate_refund()
    $this->assertSession()->pageTextContains('A refund of $59.82 USD will be credited to your account.');
    $this->assertSession()->buttonExists('Cancel Plan');

    // When the cancel form is submitted it'll trigger a PUT call to Recurly
    // which we need to mock a response for. As well as change the response
    // that is returned when retrieving the subscription so that it now shows
    // that it's been terminated. This mocks logic that happens in Recurly as
    // a result of the PUT request.
    RecurlyMockClient::addResponse('PUT', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4/terminate?refund=full&charge=true', 'subscriptions/cancel-200.xml');
    // After this the subscription response should be canceled.
    $fixture_index_200_single = [
      'fixture' => 'subscriptions/index-200-single.xml',
      'alterations' => [
        '//current_period_started_at' => date('c', strtotime('-2 weeks')),
        '//current_period_ends_at' => date('c', strtotime('+2 weeks')),
        '//state' => 'expired',
        '//updated_at' => date('c'),
        '//canceled_at' => date('c'),
        '//expires_at' => date('c'),
      ],
    ];
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', $fixture_index_200_single);

    // Submit the form. And verify the updated summary page.
    $this->getSession()->getPage()->pressButton('Cancel Plan');
    $this->assertSession()->pageTextContains('Plan Silver Plan terminated!');
    $this->assertSession()->elementTextContains('css', '.subscription .status', 'Expired');
    // Also verify that a PUT request gets made to Recurly.
    $this->assertTrue(RecurlyMockClient::assertRequestMade('PUT', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4/terminate?refund=full&charge=true'));
  }

  /**
   * Test ability to terminate subscription immediately with full refund.
   */
  public function testAdminCancelOptions() {
    RecurlyMockClient::clear();
    $this->addSharedResponses();

    $account = $this->createUserWithSubscription();

    $recurly_account = recurly_account_load([
      'entity_type' => $account->bundle(),
      'entity_id' => $account->id(),
    ], TRUE);
    $subscriptions = recurly_account_get_subscriptions($recurly_account->account_code, 'active');
    /** @var \Recurly_Subscription $subscription */
    $subscription = reset($subscriptions);

    $admin = $this->drupalCreateUser(['administer recurly', 'administer users']);
    $this->drupalLogin($admin);

    // Admin should see all cancellation options regardless of configuration.
    $this->drupalGet('/user/' . $account->id() . '/subscription/id/' . $subscription->uuid . '/cancel');
    $this->assertSession()->pageTextContains('Cancel subscription');
    $this->assertSession()->buttonExists('Cancel at Renewal');
    $this->assertSession()->pageTextContains('USD - None');
    $this->assertSession()->pageTextContains('USD - Prorated');
    $this->assertSession()->pageTextContains('USD - Full');
    $this->assertSession()->buttonExists('Terminate Immediately');

    // When the form is submitted it'll make a PUT request to Recurly to update
    // the subscription. We need to mock a response for that, and update the
    // subscription GET response to indicate that it's been cancelled in the
    // Recurly backend.
    RecurlyMockClient::addResponse('PUT', '/subscriptions/32558dd8a07eec471fbe6642d3a422f4/cancel', 'subscriptions/cancel-200.xml');
    $fixture_index_200_single = [
      'fixture' => 'subscriptions/index-200-single.xml',
      'alterations' => [
        '//current_period_started_at' => date('c', strtotime('-2 weeks')),
        '//current_period_ends_at' => date('c', strtotime('+2 weeks')),
        '//state' => 'canceled',
        '//updated_at' => date('c'),
        '//canceled_at' => date('c'),
        '//expires_at' => date('c', strtotime('+2 weeks')),
      ],
    ];
    RecurlyMockClient::addResponse('GET', '/accounts/abcdef1234567890/subscriptions?per_page=50&state=active', $fixture_index_200_single);

    // Clicking the 'Cancel at Renewal' button should work even if none of the
    // required radio options in the form are selected. This tests the
    // #limit_validation_options feature on that button.
    $this->getSession()->getPage()->pressButton('Cancel at Renewal');
    $this->assertSession()->pageTextContains('Plan Silver Plan canceled!');
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc