Skip to content
Snippets Groups Projects
OrderTest.php 32.7 KiB
Newer Older
  • Learn to ignore specific revisions
  • <?php
    /*
     +--------------------------------------------------------------------+
    
     | Copyright CiviCRM LLC. All rights reserved.                        |
    
     | This work is published under the GNU AGPLv3 license with some      |
     | permitted exceptions and without any warranty. For full license    |
     | and copyright information, see https://civicrm.org/licensing       |
    
     +--------------------------------------------------------------------+
     */
    
    
    eileen's avatar
    eileen committed
    use Civi\Api4\Contribution;
    
    use Civi\Api4\FinancialItem;
    
    /**
     *  Test APIv3 civicrm_contribute_* functions
     *
     * @package CiviCRM_APIv3
     * @subpackage API_Contribution
    
     * @group headless
    
     */
    class api_v3_OrderTest extends CiviUnitTestCase {
    
    
    eileen's avatar
    eileen committed
      use CRMTraits_Financial_TaxTrait;
    
    
      protected $_financialTypeId = 1;
    
    eileen's avatar
    eileen committed
    
    
      protected static $phpunitStartedDate;
      protected static $skipStatusCalStillExists;
    
    
      public function setUp(): void {
    
        parent::setUp();
        $this->_apiversion = 3;
    
    eileen's avatar
    eileen committed
        $this->individualCreate();
    
      }
    
      /**
       * Clean up after each test.
       */
    
      public function tearDown(): void {
    
        $this->quickCleanUpFinancialEntities();
    
        $this->quickCleanup(['civicrm_uf_match']);
    
       * Test Get order api.
    
    eileen's avatar
    eileen committed
      public function testGetOrder(): void {
    
        $contribution = $this->addOrder(FALSE, 100);
    
    
    eileen's avatar
    eileen committed
        $params = ['contribution_id' => $contribution['id']];
    
        $order = $this->callAPISuccess('Order', 'get', $params);
    
    
        $this->assertEquals(1, $order['count']);
    
        $expectedResult = [
          $contribution['id'] => [
    
            'total_amount' => 100,
            'contribution_id' => $contribution['id'],
            'contribution_status' => 'Completed',
            'net_amount' => 100,
    
          'entity_table' => 'civicrm_contribution',
          'entity_id' => $contribution['id'],
          'contribution_id' => $contribution['id'],
          'unit_price' => 100,
          'line_total' => 100,
          'financial_type_id' => 1,
    
        $this->checkPaymentResult($order, $expectedResult, $lineItems);
      }
    
    
      /**
       * Test Get Order api for participant contribution.
    
       *
       * @throws \CRM_Core_Exception
    
    eileen's avatar
    eileen committed
      public function testGetOrderParticipant(): void {
    
        $this->addOrder(FALSE, 100);
    
    eileen's avatar
    eileen committed
        $contribution = $this->createPartiallyPaidParticipantOrder();
    
          'contribution_id' => $contribution['id'],
    
    
        $order = $this->callAPISuccess('Order', 'get', $params);
    
    
        $this->assertCount(2, $order['values'][$contribution['id']]['line_items']);
    
       * Function to assert db values.
    
    eileen's avatar
    eileen committed
      public function checkPaymentResult($results, $expectedResult, $lineItems = NULL): void {
    
        foreach ($expectedResult[$results['id']] as $key => $value) {
    
          $this->assertEquals($value, $results['values'][$results['id']][$key], "Unexpected value for '$key'");
    
        }
    
        if ($lineItems) {
          foreach ($lineItems as $key => $items) {
            foreach ($items as $k => $item) {
    
              $this->assertEquals($item, $results['values'][$results['id']]['line_items'][$key][$k], "Unexpected value for line item $key, $k");
    
       *
       * @param bool $isPriceSet
       * @param float $amount
       * @param array $extraParams
       *
       * @return array
       */
    
    eileen's avatar
    eileen committed
      public function addOrder(bool $isPriceSet, float $amount = 300.00, array $extraParams = []): array {
    
    eileen's avatar
    eileen committed
          'contact_id' => $this->ids['Contact']['individual_0'],
    
          'receive_date' => '2010-01-20',
          'total_amount' => $amount,
          'financial_type_id' => $this->_financialTypeId,
          'contribution_status_id' => 1,
    
    
        if ($isPriceSet) {
          $priceFields = $this->createPriceSet();
          foreach ($priceFields['values'] as $key => $priceField) {
    
            $lineItems[1][$key] = [
    
              'price_field_id' => $priceField['price_field_id'],
              'price_field_value_id' => $priceField['id'],
              'label' => $priceField['label'],
              'field_title' => $priceField['label'],
              'qty' => 1,
              'unit_price' => $priceField['amount'],
              'line_total' => $priceField['amount'],
              'financial_type_id' => $priceField['financial_type_id'],
    
          }
          $p['line_item'] = $lineItems;
        }
        $p = array_merge($extraParams, $p);
        return $this->callAPISuccess('Contribution', 'create', $p);
      }
    
    
    eileen's avatar
    eileen committed
       * Test create order api.
    
    eileen's avatar
    eileen committed
      public function testAddOrder(): void {
    
        $order = $this->addOrder(FALSE, 100);
    
          'contribution_id' => $order['id'],
    
    eileen's avatar
    eileen committed
        $order = $this->callAPISuccess('Order', 'get', $params);
    
        $expectedResult = [
          $order['id'] => [
    
            'total_amount' => 100,
            'contribution_id' => $order['id'],
            'contribution_status' => 'Completed',
            'net_amount' => 100,
    
          'entity_table' => 'civicrm_contribution',
          'entity_id' => $order['id'],
          'contribution_id' => $order['id'],
          'unit_price' => 100,
          'line_total' => 100,
          'financial_type_id' => 1,
    
        $this->checkPaymentResult($order, $expectedResult, $lineItems);
      }
    
      /**
       * Test create order api for membership
    
       * @throws \CRM_Core_Exception
    
    eileen's avatar
    eileen committed
      public function testAddOrderForMembership(): void {
    
        $membershipType = $this->membershipTypeCreate();
        $membershipType1 = $this->membershipTypeCreate();
    
        $membershipType = $membershipTypes = [$membershipType, $membershipType1];
        $p = [
    
    eileen's avatar
    eileen committed
          'contact_id' => $this->ids['Contact']['individual_0'],
    
          'receive_date' => '2010-01-20',
    
          'financial_type_id' => 'Event Fee',
    
          'contribution_status_id' => 'Pending',
    
        $priceFields = $this->createPriceSet();
        foreach ($priceFields['values'] as $key => $priceField) {
    
            'price_field_id' => $priceField['price_field_id'],
            'price_field_value_id' => $priceField['id'],
            'label' => $priceField['label'],
            'field_title' => $priceField['label'],
            'qty' => 1,
            'unit_price' => $priceField['amount'],
            'line_total' => $priceField['amount'],
            'financial_type_id' => $priceField['financial_type_id'],
            'entity_table' => 'civicrm_membership',
            'membership_type_id' => array_pop($membershipType),
    
        $p['line_items'][] = [
          'line_item' => [array_pop($lineItems)],
          'params' => [
    
    eileen's avatar
    eileen committed
            'contact_id' => $this->ids['Contact']['individual_0'],
    
            'membership_type_id' => array_pop($membershipTypes),
            'join_date' => '2006-01-21',
            'start_date' => '2006-01-21',
            'end_date' => '2006-12-21',
            'source' => 'Payment',
            'is_override' => 1,
    
        $order = $this->callAPISuccess('Order', 'create', $p);
    
          'contribution_id' => $order['id'],
    
        $order = $this->callAPISuccess('Order', 'get', $params);
    
        $expectedResult = [
          $order['id'] => [
    
            'total_amount' => 200,
            'contribution_id' => $order['id'],
    
            'contribution_status' => 'Pending Label**',
    
        $this->checkPaymentResult($order, $expectedResult);
    
        $membershipPayment = $this->callAPISuccessGetSingle('MembershipPayment', $params);
    
    
    eileen's avatar
    eileen committed
        $this->callAPISuccessGetSingle('Membership', ['id' => $membershipPayment['id']]);
        $this->callAPISuccess('Contribution', 'Delete', ['id' => $order['id']]);
    
        $p['line_items'][] = [
          'line_item' => [array_pop($lineItems)],
          'params' => [
    
    eileen's avatar
    eileen committed
            'contact_id' => $this->ids['Contact']['individual_0'],
    
            'membership_type_id' => array_pop($membershipTypes),
            'join_date' => '2006-01-21',
            'start_date' => '2006-01-21',
            'end_date' => '2006-12-21',
            'source' => 'Payment',
            'is_override' => 1,
    
        $p['total_amount'] = 300;
    
        $order = $this->callAPISuccess('Order', 'create', $p);
    
        $expectedResult = [
          $order['id'] => [
    
            'contribution_status' => 'Pending Label**',
    
          'contribution_id' => $order['id'],
    
        $order = $this->callAPISuccess('Order', 'get', $paymentMembership);
    
        $this->checkPaymentResult($order, $expectedResult);
        $this->callAPISuccessGetCount('MembershipPayment', $paymentMembership, 2);
    
        $this->callAPISuccess('Payment', 'create', [
          'contribution_id' => $order['id'],
          'payment_instrument_id' => 'Check',
          'total_amount' => 300,
        ]);
        foreach (FinancialItem::get(FALSE)
          ->addJoin(
            'LineItem AS line_item',
            'INNER',
            NULL,
            ['entity_table', '=', '"civicrm_line_item"'],
            ['entity_id', '=', 'line_item.id'],
            ['line_item.contribution_id', '=', $order['id']]
          )
          ->addSelect('status_id')->execute() as $item) {
          $this->assertEquals('Paid', CRM_Core_PseudoConstant::getName('CRM_Financial_BAO_FinancialItem', 'status_id', $item['status_id']));
        }
    
       * Test create order api for membership.
    
       * @dataProvider dataForTestAddOrderForMembershipWithDates
       *
       * @param array $membershipExtraParams Optional additional params for the membership,
    
       *    e.g. skipStatusCal or start_date. This can also have a 'renewalOf' key, in which
       *    case we'll create an existing membership based on the values therein and try to renew it.
    
       * @param ?string $paymentDate - if set, a payment will be made on that date, completing the order. (Not implemented yet)
       * @param array $expectations
    
      public function testAddOrderForMembershipWithDates(array $membershipExtraParams, ?string $paymentDate, array $expectations): void {
    
        if (date('Y-m-d') > static::$phpunitStartedDate) {
    
          $this->markTestSkipped('Test run spanned 2 days so skipping test as results would be affected');
    
        }
        if (date('Hi') > '2357') {
    
          $this->markTestSkipped("It‘s less than 2 minutes to midnight, test skipped as 'today' may change during test.");
    
        }
        if (isset($membershipExtraParams['skipStatusCal']) && !$this->skipStatusCalStillExists()) {
    
          $this->markTestSkipped('The test was skipped as skipStatusCal seems to have been removed, so this test is useless and should be removed.');
    
        $membershipType = $this->membershipTypeCreate();
    
    
        if (isset($membershipExtraParams['renewalOf'])) {
          // Create a pre-existing membership
          $originalMembershipID = $this->callAPISuccess('Membership', 'create',
            $membershipExtraParams['renewalOf']
            + [
    
    eileen's avatar
    eileen committed
              'contact_id'         => $this->ids['Contact']['individual_0'],
    
              'membership_type_id' => $membershipType,
              'source'             => 'Old',
            ])['id'];
          unset($membershipExtraParams['renewalOf']);
          // To make this Order a renewal, we provide the ID of the membership.
          $membershipExtraParams['id'] = $originalMembershipID;
        }
    
    
        // Use the 2nd and last price field value defined in the fixture, which has value 200
        $priceFieldValue = end($this->createPriceSet()['values']);
        $orderCreateParams = [
    
    eileen's avatar
    eileen committed
          'contact_id'             => $this->ids['Contact']['individual_0'],
    
          'financial_type_id'      => 'Member Dues',
    
          'line_items'             => [[
            'line_item' => [[
              'price_field_id'       => $priceFieldValue['price_field_id'],
              'price_field_value_id' => $priceFieldValue['id'],
              'label'                => $priceFieldValue['label'],
              'field_title'          => $priceFieldValue['label'],
              'qty'                  => 1,
              'unit_price'           => $priceFieldValue['amount'],
              'line_total'           => $priceFieldValue['amount'],
              'financial_type_id'    => $priceFieldValue['financial_type_id'],
              'entity_table'         => 'civicrm_membership',
              'membership_type_id'   => $membershipType,
            ],
            ],
            'params' => [
    
    eileen's avatar
    eileen committed
              'contact_id'         => $this->ids['Contact']['individual_0'],
    
              'membership_type_id' => $membershipType,
              'source'             => 'Payment',
            ] + $membershipExtraParams,
          ],
    
        $order = $this->callAPISuccess('Order', 'create', $orderCreateParams);
    
    
        // Create expected dates immediately before order creation to minimise chance of day changing over.
    
        //$expectedStart = date('Y-m-d'); $expectedEnd = date('Y-m-d', strtotime('+ 1 year - 1 day'));
        $order = $this->callAPISuccess('Order', 'get', ['id' => $order['id']]);
    
        $expectedResult = [
          $order['id'] => [
            'total_amount' => 200,
            'contribution_id' => $order['id'],
            'contribution_status' => 'Pending Label**',
            'net_amount' => 200,
          ],
        ];
        $this->checkPaymentResult($order, $expectedResult);
    
    
        // If we are to make a payment to complete this order, do that now.
        if ($paymentDate) {
          $paymentCreateResult = $this->callAPISuccess('Payment', 'create', [
            'contribution_id'                   => $order['id'],
            'total_amount'                      => 200,
            'trxn_date'                         => $paymentDate,
            'is_send_contribution_notification' => FALSE,
          ]);
          // Reload the order, check it's now completed.
          $order = $this->callAPISuccess('Order', 'get', ['id' => $order['id']]);
          $expectedResult[$order['id']]['contribution_status'] = 'Completed';
          $this->checkPaymentResult($order, $expectedResult);
        }
    
        // Check membership details
    
        $membershipPayment = $this->callAPISuccessGetSingle('MembershipPayment', ['contribution_id' => $order['id']]);
    
        $membership = $this->callAPISuccessGetSingle('Membership', ['id' => $membershipPayment['id']]);
    
    
        if (isset($expectations['status_id'])) {
          $membership['status_id'] = \CRM_Member_PseudoConstant::membershipstatus()[$membership['status_id']];
        }
        $actuals = [];
    
        foreach ($expectations as $field => $expectedValue) {
    
          $actuals[$field] = $membership[$field] ?? NULL;
    
        $this->assertEquals($expectations, $actuals, "Membership expectations are not met");
    
        $this->callAPISuccess('Contribution', 'Delete', ['id' => $order['id']]);
      }
    
      /**
    
       * Provides data for testAddOrderForMembershipWithDates.
       *
       * As well as checking various things work as expected, this set of tests is
       * here to set out what IS expected behaviour.
    
    eileen's avatar
    eileen committed
      public function dataForTestAddOrderForMembershipWithDates(): array {
    
        // Prevent test mis-fires because of running over midnight.
        static::$phpunitStartedDate = date('Y-m-d');
    
        $today = date('Y-m-d');
        $aYearLater = date('Y-m-d', strtotime('+1 year'));
        $fromTodayForAYear = ['start_date' => $today, 'join_date' => $today, 'end_date' => date('Y-m-d', strtotime('+1 year - 1 day'))];
        $historical = ['start_date' => '2020-01-01', 'join_date' => '2020-01-01', 'end_date' => '2020-12-31'];
        $currentMembership = [
          'join_date'  => date('Y-m-d', strtotime('-9 months')),
          'start_date' => date('Y-m-d', strtotime('-9 months')),
          'end_date'   => date('Y-m-d', strtotime('-9 months + 1 year - 1 day')),
    
    eileen's avatar
    eileen committed
          // #0 Without dates, we should get a pending membership starting today.
    
          [[], NULL, $fromTodayForAYear + ['status_id' => 'Pending']],
          // #1 With our own dates.
          [$historical + ['skipStatusCal' => 1], NULL, $historical + ['status_id' => 'Pending']],
          // #2 Auto dates (from today), payment today.
          [[], $today , $fromTodayForAYear + ['status_id' => 'New']],
          // #3 With our own dates, payment today: i.e. payment date should not affect membership date.
          [$historical, $today, $historical + ['status_id' => 'Expired']],
          // #4 Renewal that is not yet paid: we would expect Order.create NOT to change
          // dates or status on the current membership (...until it gets paid, see next tests).
          [['renewalOf' => []], NULL, $fromTodayForAYear + ['status_id' => 'New']],
          // #5 Existing expired membership gets renewed today.
          [$historical + ['renewalOf' => $historical], $today, [
            'join_date'  => $historical['join_date'], /* unchanged */
            'start_date' => $today,
            'end_date'   => $fromTodayForAYear['end_date'],
            'status_id' => 'Current', /* Hmmm. */
          ],
          ],
          // #6 Existing current membership gets renewed today.
          // We expect that it adds a concurrent membership year, rather than overlapping.
          [['renewalOf' => $currentMembership], $today, [
            'join_date'  => $currentMembership['join_date'], /* unchanged */
            'start_date' => $currentMembership['start_date'], /* also unchanged */
            'end_date'   => date('Y-m-d', strtotime("$currentMembership[end_date] +1 year")),
            'status_id' => 'Current',
          ],
          ],
        ];
    
        // Duplicate the tests with skipStatusCal: i.e. should return the same
        // results for all these tests which use the Order API.
        // (This deprecated parameter should only affect a direct api3 Membership.create call.)
        // Remove this code (and skipStatusCalStillExists etc) if/when that is finally deprecated.
        foreach ($tests as $test) {
          $test[0] += ['skipStatusCal' => 1];
          $tests[] = $test;
        }
    
        return $tests;
      }
    
      /**
       * We can lose a bunch of tests when skipStatusCal is finally gone.
       */
      public function skipStatusCalStillExists() {
        if (!isset(static::$skipStatusCalStillExists)) {
          $path = Civi::paths()->getPath('[civicrm.root]/api/v3/Membership.php');
          static::$skipStatusCalStillExists = stripos(file_get_contents($path), 'skipStatusCal') !== FALSE;
        }
        return static::$skipStatusCalStillExists;
    
      /**
       * Test create order api for participant
    
    eileen's avatar
    eileen committed
      public function testAddOrderForParticipant(): void {
    
        $this->eventCreatePaid();
    
    eileen's avatar
    eileen committed
          'contact_id' => $this->ids['Contact']['individual_0'],
    
          'receive_date' => '2010-01-20',
          'financial_type_id' => $this->_financialTypeId,
    
          'contribution_status_id' => 'Pending',
    
        $priceFields = $this->createPriceSet();
        foreach ($priceFields['values'] as $key => $priceField) {
    
            'price_field_id' => $priceField['price_field_id'],
            'price_field_value_id' => $priceField['id'],
            'label' => $priceField['label'],
            'field_title' => $priceField['label'],
            'qty' => 1,
            'unit_price' => $priceField['amount'],
            'line_total' => $priceField['amount'],
            'financial_type_id' => $priceField['financial_type_id'],
            'entity_table' => 'civicrm_participant',
    
        $p['line_items'][] = [
    
          'line_item' => $lineItems,
    
    eileen's avatar
    eileen committed
            'contact_id' => $this->ids['Contact']['individual_0'],
    
            'event_id' => $this->getEventID(),
    
            'role_id' => 1,
            'register_date' => '2007-07-21 00:00:00',
            'source' => 'Online Event Registration: API Testing',
    
        $order = $this->callAPISuccess('order', 'create', $p);
    
        $params = ['contribution_id' => $order['id']];
    
        $order = $this->callAPISuccess('order', 'get', $params);
    
        $expectedResult = [
          $order['id'] => [
    
            'total_amount' => 300,
            'contribution_id' => $order['id'],
    
            'contribution_status' => 'Pending Label**',
    
        $this->checkPaymentResult($order, $expectedResult);
    
        $paymentParticipant = $this->callAPISuccessGetSingle('ParticipantPayment', ['contribution_id' => $order['id']]);
        $participant = $this->callAPISuccessGetSingle('Participant', ['participant_id' => $paymentParticipant['participant_id']]);
        $this->assertEquals('Pending (incomplete transaction)', $participant['participant_status']);
    
        $this->callAPISuccess('Contribution', 'Delete', [
    
        // Enable the "Pending from approval" status which is not enabled by default
        $pendingFromApprovalParticipantStatus = civicrm_api3('ParticipantStatusType', 'getsingle', [
    
          'name' => 'Pending from approval',
    
        ]);
        civicrm_api3('ParticipantStatusType', 'create', [
          'id' => $pendingFromApprovalParticipantStatus['id'],
    
          'name' => 'Pending from approval',
    
        $p['line_items'][] = [
    
          'line_item' => $lineItems,
    
            'contact_id' => $this->individualCreate(),
    
            'event_id' => $this->getEventID(),
    
            'role_id' => 1,
            'register_date' => '2007-07-21 00:00:00',
            'source' => 'Online Event Registration: API Testing',
    
            'participant_status_id' => 'Pending from approval',
    
        $order = $this->callAPISuccess('order', 'create', $p);
    
        $expectedResult = [
          $order['id'] => [
    
            'contribution_status' => 'Pending Label**',
    
          'contribution_id' => $order['id'],
    
        $order = $this->callAPISuccess('order', 'get', $orderParams);
    
        $this->checkPaymentResult($order, $expectedResult);
    
        $paymentParticipant = $this->callAPISuccess('ParticipantPayment', 'get', $orderParams)['values'];
    
    eileen's avatar
    eileen committed
        $this->assertCount(2, $paymentParticipant, 'Expected two participant payments');
    
        $participant = $this->callAPISuccessGetSingle('Participant', ['participant_id' => end($paymentParticipant)['participant_id']]);
        $this->assertEquals('Pending from approval', $participant['participant_status']);
    
        $this->callAPISuccess('Contribution', 'Delete', [
    
    eileen's avatar
    eileen committed
       * Test create order api with line items.
    
    eileen's avatar
    eileen committed
      public function testAddOrderWithLineItems(): void {
    
        $order = $this->addOrder(TRUE);
    
          'contribution_id' => $order['id'],
    
        $order = $this->callAPISuccess('order', 'get', $params);
    
        $expectedResult = [
          $order['id'] => [
    
            'total_amount' => 300,
            'contribution_id' => $order['id'],
            'contribution_status' => 'Completed',
            'net_amount' => 300,
    
          'entity_table' => 'civicrm_contribution',
          'entity_id' => $order['id'],
          'contribution_id' => $order['id'],
          'unit_price' => 100,
          'line_total' => 100,
    
          'entity_table' => 'civicrm_contribution',
          'entity_id' => $order['id'],
          'contribution_id' => $order['id'],
          'unit_price' => 200,
          'line_total' => 200,
    
        $this->checkPaymentResult($order, $expectedResult, $items);
    
          'entity_table' => 'civicrm_contribution',
          'entity_id' => $order['id'],
    
        $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
    
    eileen's avatar
    eileen committed
        $this->assertEquals(300, $eft['values'][$eft['id']]['amount']);
    
          'entity_table' => 'civicrm_financial_item',
          'financial_trxn_id' => $eft['values'][$eft['id']]['financial_trxn_id'],
    
        $eft = $this->callAPISuccess('EntityFinancialTrxn', 'get', $params);
    
        $amounts = [200, 100];
    
        foreach ($eft['values'] as $value) {
          $this->assertEquals($value['amount'], array_pop($amounts));
        }
    
        $this->callAPISuccess('Contribution', 'Delete', [
    
    eileen's avatar
    eileen committed
       * Test delete order api.
    
    eileen's avatar
    eileen committed
      public function testDeleteOrder(): void {
    
        $order = $this->addOrder(FALSE, 100);
    
          'contribution_id' => $order['id'],
    
        try {
          $this->callAPISuccess('order', 'delete', $params);
          $this->fail("Missed expected exception");
        }
        catch (Exception $expected) {
    
          $this->callAPISuccess('Contribution', 'create', [
    
            'contribution_id' => $order['id'],
            'is_test' => TRUE,
    
          $this->callAPISuccess('order', 'delete', $params);
    
          $order = $this->callAPISuccess('order', 'get', $params);
          $this->assertEquals(0, $order['count']);
        }
      }
    
    
    Eileen McNaughton's avatar
    Eileen McNaughton committed
      /**
       * Test order api treats amount as inclusive when line items not set.
       *
       * @throws \CRM_Core_Exception
       */
      public function testAPIOrderTaxSpecified(): void {
        $this->enableTaxAndInvoicing();
        $this->createFinancialTypeWithSalesTax();
        $order = $this->callAPISuccess('Order', 'create', [
          'total_amount' => 105,
          'financial_type_id' => 'Test taxable financial Type',
          'contact_id' => $this->individualCreate(),
          'sequential' => 1,
          'tax_amount' => 5,
        ])['values'][0];
        $this->assertEquals(105, $order['total_amount']);
        $this->assertEquals(5, $order['tax_amount']);
      }
    
      /**
       * Test order api treats amount as inclusive when line items not set.
       *
       * @throws \CRM_Core_Exception
       */
      public function testAPIOrderTaxNotSpecified(): void {
        $this->enableTaxAndInvoicing();
        $this->createFinancialTypeWithSalesTax();
        $order = $this->callAPISuccess('Order', 'create', [
          'total_amount' => 105,
          'financial_type_id' => 'Test taxable financial Type',
          'contact_id' => $this->individualCreate(),
          'sequential' => 1,
        ])['values'][0];
        $this->assertEquals(105, $order['total_amount']);
        $this->assertEquals(5, $order['tax_amount']);
      }
    
      /**
       * Test order api treats amount as inclusive when line items not set.
       *
       * @throws \CRM_Core_Exception
       */
      public function testAPIContributionTaxSpecified(): void {
        $this->enableTaxAndInvoicing();
        $this->createFinancialTypeWithSalesTax();
    
        $order = $this->callAPISuccess('Contribution', 'create', [
          'total_amount' => 105,
          'financial_type_id' => 'Test taxable financial Type',
          'contact_id' => $this->individualCreate(),
          'sequential' => 1,
          'tax_amount' => 5,
        ])['values'][0];
        $this->assertEquals(105, $order['total_amount']);
        $this->assertEquals(5, $order['tax_amount']);
      }
    
    
    eileen's avatar
    eileen committed
       * Test cancel order api.
    
    eileen's avatar
    eileen committed
      public function testCancelOrder(): void {
    
        $contribution = $this->addOrder(FALSE, 100);
    
          'contribution_id' => $contribution['id'],
    
        $this->callAPISuccess('order', 'cancel', $params);
    
        $order = $this->callAPISuccess('Order', 'get', $params);
    
        $expectedResult = [
          $contribution['id'] => [
    
            'total_amount' => 100,
            'contribution_id' => $contribution['id'],
            'contribution_status' => 'Cancelled',
            'net_amount' => 100,
    
        $this->checkPaymentResult($order, $expectedResult);
    
        $this->callAPISuccess('Contribution', 'Delete', [
    
          'id' => $contribution['id'],
    
       * Test an exception is thrown if line items do not add up to total_amount, no tax.
    
    eileen's avatar
    eileen committed
      public function testCreateOrderIfTotalAmountDoesNotMatchLineItemsAmountsIfNoTaxSupplied(): void {
    
    eileen's avatar
    eileen committed
          'contact_id' => $this->ids['Contact']['individual_0'],
    
          'receive_date' => '2018-01-01',
          'total_amount' => 50,
          'financial_type_id' => $this->_financialTypeId,
    
          'contribution_status_id' => 'Pending',
    
          'line_items' => [
            0 => [
              'line_item' => [
                '0' => [
                  'price_field_id' => 1,
                  'price_field_value_id' => 1,
                  'label' => 'Test 1',
                  'field_title' => 'Test 1',
                  'qty' => 1,
                  'unit_price' => 40,
                  'line_total' => 40,
                  'financial_type_id' => 1,
                  'entity_table' => 'civicrm_contribution',
                ],
    
    eileen's avatar
    eileen committed
        $this->callAPIFailure('Order', 'create', $params, "Line item total doesn't match total amount");
    
       * Test an exception is thrown if line items do not add up to total_amount, with tax.
    
    eileen's avatar
    eileen committed
      public function testCreateOrderIfTotalAmountDoesNotMatchLineItemsAmountsIfTaxSupplied(): void {
    
    eileen's avatar
    eileen committed
          'contact_id' => $this->ids['Contact']['individual_0'],
    
          'receive_date' => '2018-01-01',
          'total_amount' => 50,
          'financial_type_id' => $this->_financialTypeId,
    
          'contribution_status_id' => 'Pending',
    
          'tax_amount' => 15,
          'line_items' => [
            0 => [
              'line_item' => [
                '0' => [
                  'price_field_id' => 1,
                  'price_field_value_id' => 1,
                  'label' => 'Test 1',
                  'field_title' => 'Test 1',
                  'qty' => 1,
                  'unit_price' => 30,
                  'line_total' => 30,
                  'financial_type_id' => 1,
                  'entity_table' => 'civicrm_contribution',
                  'tax_amount' => 15,
                ],
    
    eileen's avatar
    eileen committed
        $this->callAPIFailure('Order', 'create', $params, "Line item total doesn't match total amount.");
    
    eileen's avatar
    eileen committed
      /**
       * @throws \CRM_Core_Exception
       */
      public function testCreateOrderIfTotalAmountDoesMatchLineItemsAmountsAndTaxSupplied(): void {
    
        $this->enableTaxAndInvoicing();
        $this->createFinancialTypeWithSalesTax();
    
    eileen's avatar
    eileen committed
          'contact_id' => $this->ids['Contact']['individual_0'],
    
          'total_amount' => 36.75,
    
          'contribution_status_id' => 'Pending',
    
          'tax_amount' => 1.75,
    
          'line_items' => [
            0 => [
              'line_item' => [
                '0' => [
                  'price_field_id' => 1,
                  'price_field_value_id' => 1,
                  'label' => 'Test 1',
                  'field_title' => 'Test 1',
                  'qty' => 1,
                  'unit_price' => 35,
                  'line_total' => 35,
    
                  'financial_type_id' => $this->ids['FinancialType']['taxable'],
    
                  'tax_amount' => 1.75,
    
        $order = $this->callAPISuccess('Order', 'create', $params);
    
    eileen's avatar
    eileen committed
       * Test that a contribution can be added in pending mode with a chained
       * payment.
    
    eileen's avatar
    eileen committed
       * We have just deprecated creating an order with a status other than
       * pending. It makes sense to support adding a payment straight away by
       * chaining.
    
    eileen's avatar
    eileen committed
      public function testCreateWithChainedPayment(): void {
    
    eileen's avatar
    eileen committed
        $contributionID = $this->callAPISuccess('Order', 'create', ['contact_id' => $this->ids['Contact']['individual_0'], 'total_amount' => 5, 'financial_type_id' => 2, 'contribution_status_id' => 'Pending', 'api.Payment.create' => ['total_amount' => 5]])['id'];
    
        $this->assertEquals('Completed', $this->callAPISuccessGetValue('Contribution', ['id' => $contributionID, 'return' => 'contribution_status']));
      }
    
    
    eileen's avatar
    eileen committed
      /**
       * Test creating an order with a mixture of taxable & non-taxable.
       *
       * @throws \CRM_Core_Exception
       */
      public function testOrderWithMixedTax(): void {
        $this->enableTaxAndInvoicing();
        $this->createFinancialTypeWithSalesTax('woo', [], ['tax_rate' => 19.3791]);
        $membershipTypeID = $this->membershipTypeCreate();
        $contactID = $this->individualCreate();
        $this->callAPISuccess('Order', 'create', [
          'contact_id' => $contactID,
          'financial_type_id' => $this->ids['FinancialType']['woo'],
          'payment_instrument_id' => 4,
          'trxn_id' => 'WooCommerce Order - 1859',
          'invoice_id' => '1859_woocommerce',
          'receive_date' => '2021-05-05 23:24:02',
          'contribution_status_id' => 'Pending',
          'source' => 'Shop',
          'note' => 'Fundraiser Dinner Ticket x 1, Student Membership x 1',
          'line_items' => [
            [
              'line_item' => [
                [
                  'price_field_id' => 1,
                  'unit_price' => 50.00,
                  'qty' => 1,
                  'line_total' => 50.00,
                  'tax_amount' => 9.69,
                  'label' => 'Fundraiser Dinner Ticket',
                  'financial_type_id' => $this->ids['FinancialType']['woo'],
                ],
              ],
            ],
            [
              'line_item' => [
                [
                  'price_field_id' => 1,
                  'unit_price' => 50.00,
                  'qty' => 1,
                  'line_total' => 50.00,
                  'tax_amount' => 0.00,
                  'label' => 'Student Membership',
                  'financial_type_id' => 2,
                  'entity_table' => 'civicrm_membership',
                  'membership_type_id' => $membershipTypeID,
                ],
              ],
              'params' => [
                'membership_type_id' => $membershipTypeID,
                'source' => 'Shop',
                'contact_id' => $contactID,
                'skipStatusCal' => 1,
                'status_id' => 'Pending',
              ],
            ],
          ],
          'tax_amount' => 9.69,
        ]);
    
    
        $contribution = Contribution::get(FALSE)
          ->addWhere('trxn_id', '=', 'WooCommerce Order - 1859')
          ->setSelect(['tax_amount', 'total_amount'])->execute()->first();
    
        $this->assertEquals('9.69', $contribution['tax_amount']);
        $this->assertEquals('109.69', $contribution['total_amount']);
        Contribution::update()->setValues([
          'source' => 'new one',
          'financial_type_id' => $this->ids['FinancialType']['woo'],
        ])->addWhere('id', '=', $contribution['id'])->execute();
    
    
    eileen's avatar
    eileen committed
        $contribution = Contribution::get(FALSE)
          ->addWhere('trxn_id', '=', 'WooCommerce Order - 1859')
          ->setSelect(['tax_amount', 'total_amount'])->execute()->first();
    
        $this->assertEquals('9.69', $contribution['tax_amount']);
        $this->assertEquals('109.69', $contribution['total_amount']);
      }