Smarty modifier - stop using isset to check taxTerm
[civicrm-core.git] / tests / phpunit / api / v3 / ContributionTest.php
index 81976a18780de6a1cec4003fc6dcd3f0f9302271..113083f59454167706bd90ac971881879a7df279 100644 (file)
@@ -9,6 +9,7 @@
  +--------------------------------------------------------------------+
  */
 
+use Civi\Api4\ActivityContact;
 use Civi\Api4\Contribution;
 use Civi\Api4\PriceField;
 use Civi\Api4\PriceFieldValue;
@@ -72,8 +73,6 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
 
   /**
    * Setup function.
-   *
-   * @throws \CiviCRM_API3_Exception
    */
   public function setUp(): void {
     parent::setUp();
@@ -119,8 +118,8 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
   /**
    * Clean up after each test.
    *
+   * @throws \API_Exception
    * @throws \CRM_Core_Exception
-   * @throws \CiviCRM_API3_Exception
    */
   public function tearDown(): void {
     $this->quickCleanUpFinancialEntities();
@@ -143,8 +142,6 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
 
   /**
    * Test Get.
-   *
-   * @throws \CRM_Core_Exception
    */
   public function testGetContribution(): void {
     $this->enableTaxAndInvoicing();
@@ -279,8 +276,6 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
 
   /**
    * Test the 'return' param works for all fields.
-   *
-   * @throws \CRM_Core_Exception
    */
   public function testGetContributionReturnFunctionality(): void {
     $params = $this->_params;
@@ -315,7 +310,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     // update contribution with invoice number
     $params = array_merge($params, [
       'id' => $contributionID,
-      'invoice_number' => CRM_Utils_Array::value('invoice_prefix', Civi::settings()->get('contribution_invoice_settings')) . "" . $contributionID,
+      'invoice_number' => Civi::settings()->get('invoice_prefix') . $contributionID,
       'trxn_id' => 12345,
       'invoice_id' => 6789,
     ]);
@@ -352,7 +347,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       if ($returnField === 'contribution_contact_id') {
         $returnField = 'contact_id';
       }
-      $this->assertTrue((!empty($contribution[$returnField]) || $contribution[$returnField] === "0"), $returnField);
+      $this->assertTrue((!empty($contribution[$returnField]) || $contribution[$returnField] === '0'), $returnField);
     }
     $entityFinancialTrxn = $this->callAPISuccess('EntityFinancialTrxn', 'get', [
       'entity_id' => $contributionID,
@@ -490,7 +485,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, __FILE__);
 
     $params = $this->_params;
-    $params['custom_' . $ids['custom_field_id']] = "custom string";
+    $params['custom_' . $ids['custom_field_id']] = 'custom string';
 
     $result = $this->callAPIAndDocument($this->entity, 'create', $params, __FUNCTION__, __FILE__);
     $this->assertEquals($result['id'], $result['values'][$result['id']]['id']);
@@ -1024,8 +1019,8 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
    * This is the test for creating soft credits.
    */
   public function testCreateContributionWithSoftCredit() {
-    $description = "Demonstrates creating contribution with SoftCredit.";
-    $subfile = "ContributionCreateWithSoftCredit";
+    $description = 'Demonstrates creating contribution with SoftCredit.';
+    $subfile = 'ContributionCreateWithSoftCredit';
     $contact2 = $this->callAPISuccess('Contact', 'create', [
       'display_name' => 'superman',
       'contact_type' => 'Individual',
@@ -1049,8 +1044,8 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
   }
 
   public function testCreateContributionWithSoftCreditDefaults() {
-    $description = "Demonstrates creating contribution with Soft Credit defaults for amount and type.";
-    $subfile = "ContributionCreateWithSoftCreditDefaults";
+    $description = 'Demonstrates creating contribution with Soft Credit defaults for amount and type.';
+    $subfile = 'ContributionCreateWithSoftCreditDefaults';
     $contact2 = $this->callAPISuccess('Contact', 'create', [
       'display_name' => 'superman',
       'contact_type' => 'Individual',
@@ -1064,7 +1059,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $this->assertEquals($contact2['id'], $result['values'][0]['soft_credit'][1]['contact_id']);
     // Default soft credit amount = contribution.total_amount
     $this->assertEquals($this->_params['total_amount'], $result['values'][0]['soft_credit'][1]['amount']);
-    $this->assertEquals(CRM_Core_OptionGroup::getDefaultValue("soft_credit_type"), $result['values'][0]['soft_credit'][1]['soft_credit_type']);
+    $this->assertEquals(CRM_Core_OptionGroup::getDefaultValue('soft_credit_type'), $result['values'][0]['soft_credit'][1]['soft_credit_type']);
 
     $this->callAPISuccess('contribution', 'delete', ['id' => $contribution['id']]);
     $this->callAPISuccess('contact', 'delete', ['id' => $contact2['id']]);
@@ -1074,8 +1069,8 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
    * Test creating contribution with Soft Credit by passing in honor_contact_id.
    */
   public function testCreateContributionWithHonoreeContact() {
-    $description = "Demonstrates creating contribution with Soft Credit by passing in honor_contact_id.";
-    $subfile = "ContributionCreateWithHonoreeContact";
+    $description = 'Demonstrates creating contribution with Soft Credit by passing in honor_contact_id.';
+    $subfile = 'ContributionCreateWithHonoreeContact';
     $contact2 = $this->callAPISuccess('Contact', 'create', [
       'display_name' => 'superman',
       'contact_type' => 'Individual',
@@ -1287,7 +1282,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $this->assertEquals($contribution['values'][$contribution['id']]['trxn_id'], 12345);
     $this->assertEquals($contribution['values'][$contribution['id']]['invoice_id'], 67890);
     $this->assertEquals($contribution['values'][$contribution['id']]['source'], 'SSF');
-    $this->assertEquals($contribution['values'][$contribution['id']]['contribution_status_id'], 2);
+    $this->assertEquals(2, $contribution['values'][$contribution['id']]['contribution_status_id']);
     $this->_checkFinancialRecords($contribution, 'pending');
   }
 
@@ -2077,9 +2072,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'currency:::USD',
       'receive_date:::' . date('Ymd', strtotime($contribution['receive_date'])),
       "receipt_date:::\n",
-      'contributeMode:::notify',
       'title:::Contribution',
-      'displayName:::Mr. Anthony Anderson II',
       'contributionStatus:::Completed',
     ]);
     $mut->stop();
@@ -2089,7 +2082,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
   /**
    * Test completing a transaction via the API with a non-USD transaction.
    */
-  public function testCompleteTransactionEuro() {
+  public function testCompleteTransactionEuro(): void {
     $mut = new CiviMailUtils($this, TRUE);
     $this->swapMessageTemplateForTestTemplate();
     $this->createLoggedInUser();
@@ -2117,9 +2110,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'currency:::EUR',
       'receive_date:::' . date('Ymd', strtotime($contribution['receive_date'])),
       "receipt_date:::\n",
-      'contributeMode:::notify',
       'title:::Contribution',
-      'displayName:::Mr. Anthony Anderson II',
       'contributionStatus:::Completed',
     ]);
     $mut->stop();
@@ -2155,7 +2146,6 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'is_pay_later:::1',
       'email:::anthony_anderson@civicrm.org',
       'pay_later_receipt:::This is a pay later receipt',
-      'displayName:::Mr. Anthony Anderson II',
       'contributionPageId:::' . $contributionPageID,
       'title:::Test Contribution Page',
       'amount:::100',
@@ -2655,8 +2645,8 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
    */
   public function contributionStatusProvider() {
     $contributionStatuses = civicrm_api3('OptionValue', 'get', [
-      'return' => ["id", "name"],
-      'option_group_id' => "contribution_status",
+      'return' => ['id', 'name'],
+      'option_group_id' => 'contribution_status',
     ]);
     foreach ($contributionStatuses['values'] as $statusName) {
       $statuses[] = [$statusName];
@@ -3190,9 +3180,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'currency:::USD',
       'receive_date:::' . date('Ymd', strtotime($receive_date)),
       'receipt_date:::' . date('Ymd'),
-      'contributeMode:::notify',
       'title:::Contribution',
-      'displayName:::Mr. Anthony Anderson II',
       'trxn_id:::kazam',
       'contactID:::' . $this->_params['contact_id'],
       'contributionID:::' . $contribution['id'],
@@ -3210,7 +3198,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $contributionPage = $this->callAPISuccess('ContributionPage', 'create', [
       'receipt_from_name' => 'Mickey Mouse',
       'receipt_from_email' => 'mickey@mouse.com',
-      'title' => "Test Contribution Page",
+      'title' => 'Test Contribution Page',
       'financial_type_id' => 1,
       'currency' => 'NZD',
       'goal_amount' => 50,
@@ -3424,49 +3412,44 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
 
   /**
    * Test membership is renewed when transaction completed.
+   *
+   * @throws \API_Exception
    */
-  public function testCompleteTransactionMembershipPriceSet() {
+  public function testCompleteTransactionMembershipPriceSet(): void {
     $this->createPriceSetWithPage('membership');
-    $stateOfGrace = $this->callAPISuccess('MembershipStatus', 'getvalue', [
-      'name' => 'Grace',
-      'return' => 'id',
+    $this->createInitialPaidMembership();
+    $membership = $this->callAPISuccess('Membership', 'getsingle', [
+      'id' => $this->getMembershipID(),
+      'status_id' => 'Grace',
+      'return' => ['end_date'],
     ]);
-    $this->setUpPendingContribution($this->_ids['price_field_value'][0]);
-    $membership = $this->callAPISuccess('membership', 'getsingle', ['id' => $this->_ids['membership']]);
-    $logs = $this->callAPISuccess('MembershipLog', 'get', [
-      'membership_id' => $this->_ids['membership'],
+    $this->assertEquals(date('Y-m-d', strtotime('yesterday')), $membership['end_date']);
+
+    $this->createSubsequentPendingMembership();
+    $this->callAPISuccess('Payment', 'create', [
+      'contribution_id' => $this->getContributionID('second'),
+      'total_amount' => 20,
     ]);
-    $this->assertEquals(1, $logs['count']);
-    $this->assertEquals($stateOfGrace, $membership['status_id']);
-    $this->callAPISuccess('contribution', 'completetransaction', ['id' => $this->_ids['contribution']]);
     $membership = $this->callAPISuccess('membership', 'getsingle', ['id' => $this->_ids['membership']]);
-    $this->assertEquals(date('Y-m-d', strtotime('yesterday + 1 year')), $membership['end_date']);
-    $this->callAPISuccessGetSingle('LineItem', [
-      'entity_id' => $this->_ids['membership'],
-      'entity_table' => 'civicrm_membership',
-    ]);
+    $this->assertEquals(date('Y-m-d', strtotime('yesterday + 2 year')), $membership['end_date']);
     $logs = $this->callAPISuccess('MembershipLog', 'get', [
-      'membership_id' => $this->_ids['membership'],
+      'membership_id' => $this->getMembershipID(),
     ]);
-    $this->assertEquals(2, $logs['count']);
-    $this->assertNotEquals($stateOfGrace, $logs['values'][2]['status_id']);
+    $this->assertEquals(4, $logs['count']);
     //Assert only three activities are created.
-    $activities = CRM_Activity_BAO_Activity::getContactActivity($this->_ids['contact']);
-    $this->assertEquals(3, count($activities));
-    $activityNames = array_flip(CRM_Utils_Array::collect('activity_name', $activities));
+    $activityNames = (array) ActivityContact::get(FALSE)
+      ->addWhere('contact_id', '=', $this->_ids['contact'])
+      ->addSelect('activity_id.activity_type_id:name')->execute()->indexBy('activity_id.activity_type_id:name');
     $this->assertArrayHasKey('Contribution', $activityNames);
     $this->assertArrayHasKey('Membership Signup', $activityNames);
     $this->assertArrayHasKey('Change Membership Status', $activityNames);
-    $this->cleanUpAfterPriceSets();
   }
 
   /**
    * Test if renewal activity is create after changing Pending contribution to
    * Completed via offline
    *
-   * @throws \CRM_Core_Exception
-   * @throws \CRM_Core_Exception
-   * @throws \CiviCRM_API3_Exception
+   * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
    */
   public function testPendingToCompleteContribution(): void {
     $this->createPriceSetWithPage('membership');
@@ -3479,12 +3462,11 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'status_id' => 'Scheduled',
     ]);
     $this->assertEquals(1, $activity['count']);
-
+    $_REQUEST['id'] = $this->getContributionID();
+    $_REQUEST['action'] = 'update';
     // change pending contribution to completed
-    $form = new CRM_Contribute_Form_Contribution();
-
-    $form->_params = [
-      'id' => $this->_ids['contribution'],
+    /* @var CRM_Contribute_Form_Contribution $form */
+    $form = $this->getFormObject('CRM_Contribute_Form_Contribution', [
       'total_amount' => 20,
       'net_amount' => 20,
       'fee_amount' => 0,
@@ -3494,7 +3476,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'billing_middle_name' => '',
       'billing_last_name' => 'Adams',
       'billing_street_address-5' => '790L Lincoln St S',
-      'billing_city-5' => 'Maryknoll',
+      'billing_city-5' => 'Mary knoll',
       'billing_state_province_id-5' => 1031,
       'billing_postal_code-5' => 10545,
       'billing_country_id-5' => 1228,
@@ -3504,28 +3486,26 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'hidden_AdditionalDetail' => 1,
       'hidden_Premium' => 1,
       'from_email_address' => '"civi45" <civi45@civicrm.com>',
-      'receipt_date' => '',
-      'receipt_date_time' => '',
       'payment_processor_id' => $this->paymentProcessorID,
       'currency' => 'USD',
       'contribution_page_id' => $this->_ids['contribution_page'],
       'source' => 'Membership Signup and Renewal',
-    ];
-
-    $form->testSubmit($form->_params, CRM_Core_Action::UPDATE);
+    ]);
+    $form->buildForm();
+    $form->postProcess();
 
     // Case 2: After successful payment for Pending backoffice there are three activities created
     //  2.a Update status of existing Scheduled Membership Signup (created in step 1) to Completed
     $activity = $this->callAPISuccess('Activity', 'get', [
       'activity_type_id' => 'Membership Signup',
-      'source_record_id' => $this->_ids['membership'],
+      'source_record_id' => $this->getMembershipID(),
       'status_id' => 'Completed',
     ]);
     $this->assertEquals(1, $activity['count']);
     // 2.b Contribution activity created to record successful payment
     $activity = $this->callAPISuccess('Activity', 'get', [
       'activity_type_id' => 'Contribution',
-      'source_record_id' => $this->_ids['contribution'],
+      'source_record_id' => $this->getContributionID(),
       'status_id' => 'Completed',
     ]);
     $this->assertEquals(1, $activity['count']);
@@ -3533,79 +3513,24 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     // 2.c 'Change membership type' activity created to record Membership status change from Grace to Current
     $activity = $this->callAPISuccess('Activity', 'get', [
       'activity_type_id' => 'Change Membership Status',
-      'source_record_id' => $this->_ids['membership'],
+      'source_record_id' => $this->getMembershipID(),
       'status_id' => 'Completed',
     ]);
     $this->assertEquals(1, $activity['count']);
-    $this->assertEquals('Status changed from Grace to Current', $activity['values'][$activity['id']]['subject']);
+    $this->assertEquals('Status changed from Pending to New', $activity['values'][$activity['id']]['subject']);
     $membershipLogs = $this->callAPISuccess('MembershipLog', 'get', ['sequential' => 1])['values'];
-    $this->assertEquals('Grace', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membershipLogs[0]['status_id']));
-    $this->assertEquals('Current', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membershipLogs[1]['status_id']));
+    $this->assertEquals('Pending', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membershipLogs[0]['status_id']));
+    $this->assertEquals('New', CRM_Core_PseudoConstant::getName('CRM_Member_BAO_Membership', 'status_id', $membershipLogs[1]['status_id']));
     //Create another pending contribution for renewal
-    $contribution = $this->callAPISuccess('contribution', 'create', [
-      'domain_id' => 1,
-      'contact_id' => $this->_ids['contact'],
-      'receive_date' => date('Ymd'),
-      'total_amount' => 20.00,
-      'financial_type_id' => 1,
-      'payment_instrument_id' => 'Credit Card',
-      'non_deductible_amount' => 10.00,
-      'trxn_id' => 'rdhfi88',
-      'invoice_id' => 'dofhiewuyr',
-      'source' => 'SSF',
-      'contribution_status_id' => 2,
-      'contribution_page_id' => $this->_ids['contribution_page'],
-      // We can't rely on contribution api to link line items correctly to membership
-      'skipLineItem' => TRUE,
-      'api.membership_payment.create' => ['membership_id' => $this->_ids['membership']],
-    ]);
-
-    $this->callAPISuccess('line_item', 'create', [
-      'entity_id' => $contribution['id'],
-      'entity_table' => 'civicrm_contribution',
-      'contribution_id' => $contribution['id'],
-      'price_field_id' => $this->_ids['price_field'][0],
-      'qty' => 1,
-      'unit_price' => 20,
-      'line_total' => 20,
-      'financial_type_id' => 1,
-      'price_field_value_id' => $this->_ids['price_field_value']['cont'],
-    ]);
-    $this->callAPISuccess('line_item', 'create', [
-      'entity_id' => $this->_ids['membership'],
-      'entity_table' => 'civicrm_membership',
-      'contribution_id' => $contribution['id'],
-      'price_field_id' => $this->_ids['price_field'][0],
-      'qty' => 1,
-      'unit_price' => 20,
-      'line_total' => 20,
-      'financial_type_id' => 1,
-      'price_field_value_id' => $this->_ids['price_field_value'][0],
-      'membership_type_id' => $this->_ids['membership_type'],
-    ]);
+    $this->setUpPendingContribution($this->_ids['price_field_value'][0], 'second', [], ['entity_id' => $this->getMembershipID()], ['id' => $this->getMembershipID()]);
 
     //Update it to Failed.
-    $form->_params['id'] = $contribution['id'];
+    $form->_params['id'] = $this->getContributionID('second');
     $form->_params['contribution_status_id'] = 4;
-    // We now have 2 line items of $20 each.
-    // form params['total_amount'] is set to $20 but
-    // now that we have 2 line items of $20 it should be $40
-    // this is an artificial test scenario.
-    // but we need to ensure it is valid enough to
-    // pass validation checks. Note the way these lines are created
-    // above is wrong wrong wrong - but the rc is not the place to fix.
-    // Also note that this test has historically created incorrect
-    // financial data. The financials check was enabled on it
-    // AFTER the code we are honing was merged.
-    // so it used to tolerate incorrect financials, then it started
-    // re-calculating them (correctly in this artificial scenario - but not for tax scenarios)
-    // but as of 5.41 it is 'accepting' the total_amount
-    // so we should pass in a correct one.
-    $form->_params['total_amount'] = 40;
 
     $form->testSubmit($form->_params, CRM_Core_Action::UPDATE);
     //Existing membership should not get updated to expired.
-    $membership = $this->callAPISuccess('membership', 'getsingle', ['id' => $this->_ids['membership']]);
+    $membership = $this->callAPISuccess('Membership', 'getsingle', ['id' => $this->_ids['membership']]);
     $this->assertNotEquals(4, $membership['status_id']);
   }
 
@@ -3614,19 +3539,25 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
    *
    * Also check that altering the qty for the most recent contribution results in repeattransaction picking it up.
    */
-  public function testCompleteTransactionMembershipPriceSetTwoTerms() {
+  public function testCompleteTransactionMembershipPriceSetTwoTerms(): void {
     $this->createPriceSetWithPage('membership');
-    $this->setUpPendingContribution($this->_ids['price_field_value'][1]);
-    $this->callAPISuccess('contribution', 'completetransaction', ['id' => $this->_ids['contribution']]);
-    $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->_ids['membership']]);
+    $this->createInitialPaidMembership();
+    $this->createSubsequentPendingMembership();
+
+    $this->callAPISuccess('Payment', 'create', [
+      'contribution_id' => $this->getContributionID('second'),
+      'total_amount' => 20,
+    ]);
+
+    $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->getMembershipID()]);
     $this->assertEquals(date('Y-m-d', strtotime('yesterday + 2 years')), $membership['end_date']);
 
     $paymentProcessorID = $this->paymentProcessorAuthorizeNetCreate();
 
     $contributionRecurID = $this->callAPISuccess('ContributionRecur', 'create', ['contact_id' => $membership['contact_id'], 'payment_processor_id' => $paymentProcessorID, 'amount' => 20, 'frequency_interval' => 1])['id'];
-    $this->callAPISuccess('Contribution', 'create', ['id' => $this->_ids['contribution'], 'contribution_recur_id' => $contributionRecurID]);
+    $this->callAPISuccess('Contribution', 'create', ['id' => $this->getContributionID(), 'contribution_recur_id' => $contributionRecurID]);
     $this->callAPISuccess('contribution', 'repeattransaction', ['contribution_recur_id' => $contributionRecurID, 'contribution_status_id' => 'Completed']);
-    $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->_ids['membership']]);
+    $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->getMembershipID()]);
     $this->assertEquals(date('Y-m-d', strtotime('yesterday + 4 years')), $membership['end_date']);
 
     // Update the most recent contribution to have a qty of 1 in it's line item and then repeat, expecting just 1 year to be added.
@@ -3635,8 +3566,6 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $this->callAPISuccess('contribution', 'repeattransaction', ['contribution_recur_id' => $contributionRecurID, 'contribution_status_id' => 'Completed']);
     $membership = $this->callAPISuccessGetSingle('membership', ['id' => $this->_ids['membership']]);
     $this->assertEquals(date('Y-m-d', strtotime('yesterday + 5 years')), $membership['end_date']);
-
-    $this->cleanUpAfterPriceSets();
   }
 
   public function cleanUpAfterPriceSets() {
@@ -3648,13 +3577,23 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
    * Set up a pending transaction with a specific price field id.
    *
    * @param int $priceFieldValueID
-   * @param array $contriParams
-   *
-   * @throws \CRM_Core_Exception
-   * @throws \CiviCRM_API3_Exception
+   * @param string $key
+   * @param array $contributionParams
+   * @param array $lineParams
+   * @param array $membershipParams
    */
-  public function setUpPendingContribution(int $priceFieldValueID, $contriParams = []): void {
+  public function setUpPendingContribution(int $priceFieldValueID, string $key = 'first', array $contributionParams = [], array $lineParams = [], array $membershipParams = []): void {
     $contactID = $this->individualCreate();
+    $membershipParams = array_merge([
+      'contact_id' => $contactID,
+      'membership_type_id' => $this->_ids['membership_type'],
+    ], $membershipParams);
+    if ($key === 'first') {
+      // If we want these after the initial we will set them.
+      $membershipParams['start_date'] = 'yesterday - 1 year';
+      $membershipParams['end_date'] = 'yesterday';
+      $membershipParams['join_date'] = 'yesterday - 1 year';
+    }
     $contribution = $this->callAPISuccess('Order', 'create', array_merge([
       'domain_id' => 1,
       'contact_id' => $contactID,
@@ -3663,15 +3602,12 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       'financial_type_id' => 1,
       'payment_instrument_id' => 'Credit Card',
       'non_deductible_amount' => 10.00,
-      'trxn_id' => 'abcd',
-      'invoice_id' => 'inv',
       'source' => 'SSF',
-      'contribution_status_id' => 2,
       'contribution_page_id' => $this->_ids['contribution_page'],
       'line_items' => [
         [
           'line_item' => [
-            [
+            array_merge([
               'price_field_id' => $this->_ids['price_field'][0],
               'qty' => 1,
               'entity_table' => 'civicrm_membership',
@@ -3679,21 +3615,15 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
               'line_total' => 20,
               'financial_type_id' => 1,
               'price_field_value_id' => $priceFieldValueID,
-            ],
-          ],
-          'params' => [
-            'contact_id' => $contactID,
-            'membership_type_id' => $this->_ids['membership_type'],
-            'start_date' => 'yesterday - 1 year',
-            'end_date' => 'yesterday',
-            'join_date' => 'yesterday - 1 year',
+            ], $lineParams),
           ],
+          'params' => $membershipParams,
         ],
       ],
-    ], $contriParams));
+    ], $contributionParams));
 
     $this->_ids['contact'] = $contactID;
-    $this->_ids['contribution'] = $contribution['id'];
+    $this->ids['contribution'][$key] = $contribution['id'];
     $this->_ids['membership'] = $this->callAPISuccessGetValue('MembershipPayment', ['return' => 'membership_id', 'contribution_id' => $contribution['id']]);
   }
 
@@ -3789,7 +3719,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
       // Need to figure out how to stop this some other day
       // We don't care about the Payment Processor because this is Pay Later
       // The point of this test is to check we get the pay_later version of the mail
-      if ($e->getMessage() !== "Undefined variable: CRM16923AnUnreliableMethodHasBeenUserToDeterminePaymentProcessorFromContributionPage") {
+      if ($e->getMessage() !== 'Undefined variable: CRM16923AnUnreliableMethodHasBeenUserToDeterminePaymentProcessorFromContributionPage') {
         throw $e;
       }
     }
@@ -4668,7 +4598,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $contributionPage = $this->callAPISuccess('ContributionPage', 'create', array_merge([
       'receipt_from_name' => 'Mickey Mouse',
       'receipt_from_email' => 'mickey@mouse.com',
-      'title' => "Test Contribution Page",
+      'title' => 'Test Contribution Page',
       'financial_type_id' => 1,
       'currency' => 'CAD',
       'is_monetary' => TRUE,
@@ -4817,7 +4747,7 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     $this->assertNotContains('$', $result['values']);
     $result = $this->callAPISuccess('Contribution', 'getoptions', [
       'field' => 'currency',
-      'context' => "abbreviate",
+      'context' => 'abbreviate',
     ]);
     $this->assertEquals('$', $result['values']['USD']);
     $this->assertNotContains('US Dollar', $result['values']);
@@ -5183,4 +5113,44 @@ class api_v3_ContributionTest extends CiviUnitTestCase {
     return $contributionPageID;
   }
 
+  /**
+   * Get the created contribution ID.
+   *
+   * @param string $key
+   *
+   * @return int
+   */
+  protected function getContributionID(string $key = 'first'): int {
+    return (int) $this->ids['contribution'][$key];
+  }
+
+  /**
+   * Get the created contribution ID.
+   *
+   * @return int
+   */
+  protected function getMembershipID(): int {
+    return (int) $this->_ids['membership'];
+  }
+
+  /**
+   * Create a paid membership for renewal tests.
+   */
+  protected function createSubsequentPendingMembership(): void {
+    $this->setUpPendingContribution($this->_ids['price_field_value'][1], 'second', [], [], [
+      'id' => $this->getMembershipID(),
+    ]);
+  }
+
+  /**
+   * Create a paid membership for renewal tests.
+   */
+  protected function createInitialPaidMembership(): void {
+    $this->setUpPendingContribution($this->_ids['price_field_value'][1]);
+    $this->callAPISuccess('Payment', 'create', [
+      'contribution_id' => $this->getContributionID(),
+      'total_amount' => 20,
+    ]);
+  }
+
 }