Commit | Line | Data |
---|---|---|
bbf58b03 EM |
1 | <?php |
2 | /* | |
3 | +--------------------------------------------------------------------+ | |
7d61e75f | 4 | | Copyright CiviCRM LLC. All rights reserved. | |
bbf58b03 | 5 | | | |
7d61e75f TO |
6 | | This work is published under the GNU AGPLv3 license with some | |
7 | | permitted exceptions and without any warranty. For full license | | |
8 | | and copyright information, see https://civicrm.org/licensing | | |
bbf58b03 | 9 | +--------------------------------------------------------------------+ |
d25dd0ee | 10 | */ |
bbf58b03 | 11 | |
ae16be86 | 12 | use Civi\Api4\ContributionRecur; |
13 | ||
7fe37828 EM |
14 | /** |
15 | * Class CRM_Contribute_BAO_ContributionRecurTest | |
acb109b7 | 16 | * @group headless |
7fe37828 | 17 | */ |
bbf58b03 | 18 | class CRM_Contribute_BAO_ContributionRecurTest extends CiviUnitTestCase { |
bbf58b03 | 19 | |
d486b01e PN |
20 | use CRMTraits_Financial_OrderTrait; |
21 | ||
a33b79a2 EM |
22 | protected $isValidateFinancialsOnPostAssert = TRUE; |
23 | ||
d486b01e PN |
24 | /** |
25 | * Set up for test. | |
26 | * | |
ae16be86 | 27 | * @throws \CiviCRM_API3_Exception |
d486b01e | 28 | */ |
ae16be86 | 29 | public function setUp(): void { |
bbf58b03 | 30 | parent::setUp(); |
ae16be86 | 31 | $this->ids['payment_processor'] = $this->paymentProcessorCreate(); |
9099cab3 | 32 | $this->_params = [ |
bbf58b03 EM |
33 | 'contact_id' => $this->individualCreate(), |
34 | 'amount' => 3.00, | |
35 | 'frequency_unit' => 'week', | |
36 | 'frequency_interval' => 1, | |
37 | 'installments' => 2, | |
38 | 'start_date' => 'yesterday', | |
39 | 'create_date' => 'yesterday', | |
40 | 'modified_date' => 'yesterday', | |
41 | 'cancel_date' => NULL, | |
42 | 'end_date' => '+ 2 weeks', | |
43 | 'processor_id' => '643411460836', | |
44 | 'trxn_id' => 'e0d0808e26f3e661c6c18eb7c039d363', | |
45 | 'invoice_id' => 'e0d0808e26f3e661c6c18eb7c039d363', | |
46 | 'contribution_status_id' => 1, | |
47 | 'is_test' => 0, | |
48 | 'cycle_day' => 1, | |
49 | 'next_sched_contribution_date' => '+ 1 week', | |
50 | 'failure_count' => 0, | |
51 | 'failure_retry_date' => NULL, | |
52 | 'auto_renew' => 0, | |
53 | 'currency' => 'USD', | |
ae16be86 | 54 | 'payment_processor_id' => $this->ids['payment_processor'], |
bbf58b03 EM |
55 | 'is_email_receipt' => 1, |
56 | 'financial_type_id' => 1, | |
57 | 'payment_instrument_id' => 1, | |
58 | 'campaign_id' => NULL, | |
9099cab3 | 59 | ]; |
bbf58b03 EM |
60 | } |
61 | ||
d486b01e PN |
62 | /** |
63 | * Cleanup after test. | |
64 | * | |
65 | * @throws \CRM_Core_Exception | |
66 | */ | |
ae16be86 | 67 | public function teardown():void { |
d486b01e | 68 | $this->quickCleanUpFinancialEntities(); |
bbf58b03 EM |
69 | } |
70 | ||
71 | /** | |
fe482240 EM |
72 | * Test that an object can be retrieved & saved (per CRM-14986). |
73 | * | |
74 | * This has been causing a DB error so we are checking for absence of error | |
d486b01e PN |
75 | * |
76 | * @throws \CRM_Core_Exception | |
bbf58b03 | 77 | */ |
ae16be86 | 78 | public function testFindSave(): void { |
bbf58b03 EM |
79 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params); |
80 | $dao = new CRM_Contribute_BAO_ContributionRecur(); | |
81 | $dao->id = $contributionRecur['id']; | |
82 | $dao->find(TRUE); | |
83 | $dao->is_email_receipt = 0; | |
84 | $dao->save(); | |
85 | } | |
86 | ||
87 | /** | |
fe482240 EM |
88 | * Test cancellation works per CRM-14986. |
89 | * | |
90 | * We are checking for absence of error. | |
d486b01e PN |
91 | * |
92 | * @throws \CRM_Core_Exception | |
bbf58b03 | 93 | */ |
ae16be86 | 94 | public function testCancelRecur(): void { |
bbf58b03 | 95 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params); |
9cfc631e | 96 | CRM_Contribute_BAO_ContributionRecur::cancelRecurContribution(['id' => $contributionRecur['id']]); |
bbf58b03 EM |
97 | } |
98 | ||
748bcfb2 | 99 | /** |
0b2f1f29 | 100 | * Test checking if contribution recur object can allow for changes to financial types. |
748bcfb2 | 101 | * |
ae16be86 | 102 | * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception |
748bcfb2 | 103 | */ |
ae16be86 | 104 | public function testSupportFinancialTypeChange(): void { |
748bcfb2 | 105 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params); |
9099cab3 | 106 | $this->callAPISuccess('Contribution', 'create', [ |
748bcfb2 SL |
107 | 'contribution_recur_id' => $contributionRecur['id'], |
108 | 'total_amount' => '3.00', | |
109 | 'financial_type_id' => 1, | |
110 | 'payment_instrument_id' => 1, | |
111 | 'currency' => 'USD', | |
112 | 'contact_id' => $this->individualCreate(), | |
113 | 'contribution_status_id' => 1, | |
46f459f2 | 114 | 'receive_date' => 'yesterday', |
9099cab3 | 115 | ]); |
748bcfb2 SL |
116 | $this->assertTrue(CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($contributionRecur['id'])); |
117 | } | |
118 | ||
3d6bf1a7 EE |
119 | /** |
120 | * Test we don't change unintended fields on API edit | |
d486b01e PN |
121 | * |
122 | * @throws \CRM_Core_Exception | |
3d6bf1a7 | 123 | */ |
ae16be86 | 124 | public function testUpdateRecur(): void { |
3d6bf1a7 EE |
125 | $createParams = $this->_params; |
126 | $createParams['currency'] = 'XAU'; | |
127 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $createParams); | |
9099cab3 | 128 | $editParams = [ |
3d6bf1a7 EE |
129 | 'id' => $contributionRecur['id'], |
130 | 'end_date' => '+ 4 weeks', | |
9099cab3 | 131 | ]; |
3d6bf1a7 EE |
132 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $editParams); |
133 | $dao = new CRM_Contribute_BAO_ContributionRecur(); | |
134 | $dao->id = $contributionRecur['id']; | |
135 | $dao->find(TRUE); | |
136 | $this->assertEquals('XAU', $dao->currency, 'Edit clobbered recur currency'); | |
137 | } | |
138 | ||
0b2f1f29 AS |
139 | /** |
140 | * Check test contributions aren't picked up as template for non-test recurs | |
141 | * | |
d486b01e PN |
142 | * @throws \API_Exception |
143 | * @throws \CRM_Core_Exception | |
144 | * @throws \CiviCRM_API3_Exception | |
145 | * @throws \Civi\API\Exception\UnauthorizedException | |
0b2f1f29 | 146 | */ |
ae16be86 | 147 | public function testGetTemplateContributionMatchTest1(): void { |
0b2f1f29 AS |
148 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params); |
149 | // Create a first contrib | |
150 | $firstContrib = $this->callAPISuccess('Contribution', 'create', [ | |
151 | 'contribution_recur_id' => $contributionRecur['id'], | |
152 | 'total_amount' => '3.00', | |
153 | 'financial_type_id' => 1, | |
154 | 'payment_instrument_id' => 1, | |
155 | 'currency' => 'USD', | |
156 | 'contact_id' => $this->individualCreate(), | |
157 | 'contribution_status_id' => 1, | |
158 | 'receive_date' => 'yesterday', | |
159 | ]); | |
160 | // Create a test contrib - should not be picked up as template for non-test recur | |
161 | $this->callAPISuccess('Contribution', 'create', [ | |
162 | 'contribution_recur_id' => $contributionRecur['id'], | |
163 | 'total_amount' => '3.00', | |
164 | 'financial_type_id' => 1, | |
165 | 'payment_instrument_id' => 1, | |
166 | 'currency' => 'USD', | |
167 | 'contact_id' => $this->individualCreate(), | |
168 | 'contribution_status_id' => 1, | |
169 | 'receive_date' => 'yesterday', | |
170 | 'is_test' => 1, | |
171 | ]); | |
172 | $fetchedTemplate = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionRecur['id']); | |
173 | $this->assertEquals($firstContrib['id'], $fetchedTemplate['id']); | |
174 | } | |
175 | ||
176 | /** | |
177 | * Check non-test contributions aren't picked up as template for test recurs | |
178 | * | |
d486b01e PN |
179 | * @throws \API_Exception |
180 | * @throws \CRM_Core_Exception | |
181 | * @throws \CiviCRM_API3_Exception | |
182 | * @throws \Civi\API\Exception\UnauthorizedException | |
0b2f1f29 | 183 | */ |
ae16be86 | 184 | public function testGetTemplateContributionMatchTest(): void { |
0b2f1f29 AS |
185 | $params = $this->_params; |
186 | $params['is_test'] = 1; | |
187 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $params); | |
188 | // Create a first test contrib | |
189 | $firstContrib = $this->callAPISuccess('Contribution', 'create', [ | |
190 | 'contribution_recur_id' => $contributionRecur['id'], | |
191 | 'total_amount' => '3.00', | |
192 | 'financial_type_id' => 1, | |
193 | 'payment_instrument_id' => 1, | |
194 | 'currency' => 'USD', | |
195 | 'contact_id' => $this->individualCreate(), | |
196 | 'contribution_status_id' => 1, | |
197 | 'receive_date' => 'yesterday', | |
198 | 'is_test' => 1, | |
199 | ]); | |
200 | // Create a non-test contrib - should not be picked up as template for non-test recur | |
201 | // This shouldn't occur - a live contrib against a test recur, but that's not the point... | |
202 | $this->callAPISuccess('Contribution', 'create', [ | |
203 | 'contribution_recur_id' => $contributionRecur['id'], | |
204 | 'total_amount' => '3.00', | |
205 | 'financial_type_id' => 1, | |
206 | 'payment_instrument_id' => 1, | |
207 | 'currency' => 'USD', | |
208 | 'contact_id' => $this->individualCreate(), | |
209 | 'contribution_status_id' => 1, | |
210 | 'receive_date' => 'yesterday', | |
211 | 'is_test' => 0, | |
212 | ]); | |
213 | $fetchedTemplate = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionRecur['id']); | |
214 | $this->assertEquals($firstContrib['id'], $fetchedTemplate['id']); | |
215 | } | |
216 | ||
7d238e35 JJ |
217 | /** |
218 | * Check whether template contribution is created based on the first contribution. | |
219 | * | |
220 | * There are three contributions created. Each of them with a different value at a custom field. | |
221 | * The first contribution created should be copied as a template contribution. | |
222 | * The other two should not be used as a template. | |
223 | * | |
224 | * Then we delete the template contribution and make sure a new one exists. | |
225 | * At that time the second contribution should be used a template as that is the most recent one (according to the date). | |
226 | * | |
227 | * @throws \API_Exception | |
228 | * @throws \CRM_Core_Exception | |
229 | * @throws \CiviCRM_API3_Exception | |
230 | * @throws \Civi\API\Exception\UnauthorizedException | |
231 | */ | |
232 | public function testCreateTemplateContributionFromFirstContributionTest(): void { | |
233 | $custom_group = $this->customGroupCreate(['extends' => 'Contribution', 'name' => 'template']); | |
234 | $custom_field = $this->customFieldCreate(['custom_group_id' => $custom_group['id'], 'name' => 'field']); | |
235 | ||
236 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params); | |
237 | // Create a first test contrib | |
238 | $date = new DateTime(); | |
239 | $firstContrib = $this->callAPISuccess('Contribution', 'create', [ | |
240 | 'contribution_recur_id' => $contributionRecur['id'], | |
241 | 'total_amount' => '3.00', | |
242 | 'financial_type_id' => 1, | |
243 | 'payment_instrument_id' => 1, | |
244 | 'currency' => 'USD', | |
245 | 'contact_id' => $this->_params['contact_id'], | |
246 | 'contribution_status_id' => 1, | |
247 | 'receive_date' => $date->format('YmdHis'), | |
248 | 'custom_' . $custom_field['id'] => 'First Contribution', | |
249 | ]); | |
250 | $date->modify('+2 days'); | |
251 | $secondContrib = $this->callAPISuccess('Contribution', 'create', [ | |
252 | 'contribution_recur_id' => $contributionRecur['id'], | |
253 | 'total_amount' => '3.00', | |
254 | 'financial_type_id' => 1, | |
255 | 'payment_instrument_id' => 1, | |
256 | 'currency' => 'USD', | |
257 | 'contact_id' => $this->_params['contact_id'], | |
258 | 'contribution_status_id' => 1, | |
259 | 'receive_date' => $date->format('YmdHis'), | |
260 | 'custom_' . $custom_field['id'] => 'Second and most recent Contribution', | |
261 | ]); | |
262 | ||
263 | $date->modify('-1 week'); | |
264 | $thirdContrib = $this->callAPISuccess('Contribution', 'create', [ | |
265 | 'contribution_recur_id' => $contributionRecur['id'], | |
266 | 'total_amount' => '3.00', | |
267 | 'financial_type_id' => 1, | |
268 | 'payment_instrument_id' => 1, | |
269 | 'currency' => 'USD', | |
270 | 'contact_id' => $this->_params['contact_id'], | |
271 | 'contribution_status_id' => 1, | |
272 | 'receive_date' => $date->format('YmdHis'), | |
273 | 'custom_' . $custom_field['id'] => 'Third Contribution', | |
274 | ]); | |
275 | ||
276 | // Make sure a template contribution exists. | |
277 | $templateContributionId = CRM_Contribute_BAO_ContributionRecur::ensureTemplateContributionExists($contributionRecur['id']); | |
278 | $fetchedTemplate = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionRecur['id']); | |
279 | $templateContribution = \Civi\Api4\Contribution::get(FALSE) | |
280 | ->addSelect('*', 'custom.*') | |
281 | ->addWhere('contribution_recur_id', '=', $contributionRecur['id']) | |
282 | ->addWhere('is_template', '=', 1) | |
283 | ->addWhere('is_test', '=', 0) | |
284 | ->addOrderBy('id', 'DESC') | |
285 | ->execute(); | |
286 | ||
287 | $this->assertNotEquals($firstContrib['id'], $fetchedTemplate['id']); | |
288 | $this->assertNotEquals($secondContrib['id'], $fetchedTemplate['id']); | |
289 | $this->assertNotEquals($thirdContrib['id'], $fetchedTemplate['id']); | |
290 | $this->assertEquals($templateContributionId, $fetchedTemplate['id']); | |
291 | $this->assertTrue($fetchedTemplate['is_template']); | |
292 | $this->assertFalse($fetchedTemplate['is_test']); | |
293 | $this->assertEquals(1, $templateContribution->count()); | |
294 | $templateContribution = $templateContribution->first(); | |
295 | $this->assertNotNull($templateContribution['template.field']); | |
296 | $this->assertEquals('Second and most recent Contribution', $templateContribution['template.field']); | |
297 | } | |
298 | ||
f306dbeb AS |
299 | /** |
300 | * Test that is_template contribution is used where available | |
301 | * | |
d486b01e | 302 | * @throws \API_Exception |
d486b01e PN |
303 | * @throws \CiviCRM_API3_Exception |
304 | * @throws \Civi\API\Exception\UnauthorizedException | |
f306dbeb | 305 | */ |
ae16be86 | 306 | public function testGetTemplateContributionNewTemplate(): void { |
f306dbeb AS |
307 | $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params); |
308 | // Create the template | |
309 | $templateContrib = $this->callAPISuccess('Contribution', 'create', [ | |
310 | 'contribution_recur_id' => $contributionRecur['id'], | |
311 | 'total_amount' => '3.00', | |
312 | 'financial_type_id' => 1, | |
c80d977f | 313 | 'source' => 'Template Contribution', |
f306dbeb | 314 | 'payment_instrument_id' => 1, |
aed0a241 | 315 | 'currency' => 'AUD', |
f306dbeb AS |
316 | 'contact_id' => $this->individualCreate(), |
317 | 'contribution_status_id' => 1, | |
318 | 'receive_date' => 'yesterday', | |
319 | 'is_template' => 1, | |
320 | ]); | |
321 | // Create another normal contrib | |
322 | $this->callAPISuccess('Contribution', 'create', [ | |
323 | 'contribution_recur_id' => $contributionRecur['id'], | |
324 | 'total_amount' => '3.00', | |
325 | 'financial_type_id' => 1, | |
c80d977f | 326 | 'source' => 'Non-template Contribution', |
f306dbeb AS |
327 | 'payment_instrument_id' => 1, |
328 | 'currency' => 'USD', | |
329 | 'contact_id' => $this->individualCreate(), | |
330 | 'contribution_status_id' => 1, | |
331 | 'receive_date' => 'yesterday', | |
332 | ]); | |
333 | $fetchedTemplate = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionRecur['id']); | |
334 | // Fetched template should be the is_template, not the latest contrib | |
335 | $this->assertEquals($fetchedTemplate['id'], $templateContrib['id']); | |
c80d977f JP |
336 | |
337 | $repeatContribution = $this->callAPISuccess('Contribution', 'repeattransaction', [ | |
338 | 'contribution_status_id' => 'Completed', | |
339 | 'contribution_recur_id' => $contributionRecur['id'], | |
340 | ]); | |
341 | $this->assertEquals('Template Contribution', $repeatContribution['values'][$repeatContribution['id']]['source']); | |
aed0a241 | 342 | $this->assertEquals('AUD', $repeatContribution['values'][$repeatContribution['id']]['currency']); |
f306dbeb AS |
343 | } |
344 | ||
d486b01e PN |
345 | /** |
346 | * Test to check if correct membership is auto renewed. | |
347 | * | |
ae16be86 | 348 | * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception |
d486b01e | 349 | */ |
ae16be86 | 350 | public function testAutoRenewalWhenOneMemberIsDeceased(): void { |
d486b01e PN |
351 | $contactId1 = $this->individualCreate(); |
352 | $contactId2 = $this->individualCreate(); | |
353 | $membershipOrganizationId = $this->organizationCreate(); | |
354 | ||
355 | $this->createExtraneousContribution(); | |
356 | $this->callAPISuccess('Contribution', 'create', [ | |
357 | 'contact_id' => $contactId1, | |
358 | 'receive_date' => '2010-01-20', | |
359 | 'financial_type_id' => 'Member Dues', | |
360 | 'contribution_status_id' => 'Completed', | |
361 | 'total_amount' => 150, | |
362 | ]); | |
363 | ||
364 | // create membership type | |
ae16be86 | 365 | $membershipTypeId1 = (int) $this->callAPISuccess('MembershipType', 'create', [ |
d486b01e PN |
366 | 'domain_id' => 1, |
367 | 'member_of_contact_id' => $membershipOrganizationId, | |
368 | 'financial_type_id' => 'Member Dues', | |
369 | 'duration_unit' => 'month', | |
370 | 'duration_interval' => 1, | |
371 | 'period_type' => 'rolling', | |
372 | 'minimum_fee' => 100, | |
373 | 'name' => 'Parent', | |
374 | ])['id']; | |
375 | ||
ae16be86 | 376 | $membershipTypeID = (int) $this->callAPISuccess('MembershipType', 'create', [ |
d486b01e PN |
377 | 'domain_id' => 1, |
378 | 'member_of_contact_id' => $membershipOrganizationId, | |
379 | 'financial_type_id' => 'Member Dues', | |
380 | 'duration_unit' => 'month', | |
381 | 'duration_interval' => 1, | |
382 | 'period_type' => 'rolling', | |
383 | 'minimum_fee' => 50, | |
384 | 'name' => 'Child', | |
385 | ])['id']; | |
386 | ||
387 | $contactIDs = [ | |
388 | $contactId1 => $membershipTypeId1, | |
389 | $contactId2 => $membershipTypeID, | |
390 | ]; | |
391 | ||
392 | $contributionRecurId = $this->callAPISuccess('contribution_recur', 'create', $this->_params)['id']; | |
393 | ||
394 | $priceFields = CRM_Price_BAO_PriceSet::getDefaultPriceSet('membership'); | |
395 | ||
396 | // prepare order api params. | |
397 | $params = [ | |
398 | 'contact_id' => $contactId1, | |
399 | 'receive_date' => '2010-01-20', | |
400 | 'financial_type_id' => 'Member Dues', | |
d486b01e PN |
401 | 'contribution_recur_id' => $contributionRecurId, |
402 | 'total_amount' => 150, | |
403 | 'api.Payment.create' => ['total_amount' => 150], | |
404 | ]; | |
405 | ||
406 | foreach ($priceFields as $priceField) { | |
407 | $lineItems = []; | |
ae16be86 | 408 | $contactId = array_search((int) $priceField['membership_type_id'], $contactIDs, TRUE); |
d486b01e PN |
409 | $lineItems[1] = [ |
410 | 'price_field_id' => $priceField['priceFieldID'], | |
411 | 'price_field_value_id' => $priceField['priceFieldValueID'], | |
412 | 'label' => $priceField['label'], | |
413 | 'field_title' => $priceField['label'], | |
414 | 'qty' => 1, | |
415 | 'unit_price' => $priceField['amount'], | |
416 | 'line_total' => $priceField['amount'], | |
417 | 'financial_type_id' => $priceField['financial_type_id'], | |
418 | 'entity_table' => 'civicrm_membership', | |
419 | 'membership_type_id' => $priceField['membership_type_id'], | |
420 | ]; | |
421 | $params['line_items'][] = [ | |
422 | 'line_item' => $lineItems, | |
423 | 'params' => [ | |
424 | 'contact_id' => $contactId, | |
425 | 'membership_type_id' => $priceField['membership_type_id'], | |
426 | 'source' => 'Payment', | |
dadbf1e3 | 427 | 'join_date' => date('Y-m', strtotime('1 month ago')) . '-28', |
fd684278 | 428 | 'start_date' => date('Y-m') . '-28', |
d486b01e PN |
429 | 'contribution_recur_id' => $contributionRecurId, |
430 | 'status_id' => 'Pending', | |
431 | 'is_override' => 1, | |
432 | ], | |
433 | ]; | |
434 | } | |
435 | $order = $this->callAPISuccess('Order', 'create', $params); | |
436 | $contributionId = $order['id']; | |
437 | $membershipId1 = $this->callAPISuccessGetValue('Membership', [ | |
438 | 'contact_id' => $contactId1, | |
439 | 'membership_type_id' => $membershipTypeId1, | |
440 | 'return' => 'id', | |
441 | ]); | |
442 | ||
443 | $membershipId2 = $this->callAPISuccessGetValue('Membership', [ | |
444 | 'contact_id' => $contactId2, | |
445 | 'membership_type_id' => $membershipTypeID, | |
446 | 'return' => 'id', | |
447 | ]); | |
448 | ||
449 | // First renewal (2nd payment). | |
450 | $this->callAPISuccess('Contribution', 'repeattransaction', [ | |
451 | 'original_contribution_id' => $contributionId, | |
452 | 'contribution_status_id' => 'Completed', | |
453 | ]); | |
454 | ||
455 | // Second Renewal (3rd payment). | |
456 | $this->callAPISuccess('Contribution', 'repeattransaction', [ | |
457 | 'original_contribution_id' => $contributionId, | |
458 | 'contribution_status_id' => 'Completed', | |
459 | ]); | |
460 | ||
461 | // Third renewal (4th payment). | |
462 | $this->callAPISuccess('Contribution', 'repeattransaction', ['original_contribution_id' => $contributionId, 'contribution_status_id' => 'Completed']); | |
463 | ||
464 | // check line item and membership payment count. | |
465 | $this->validateAllCounts($membershipId1, 4); | |
466 | $this->validateAllCounts($membershipId2, 4); | |
467 | ||
e9b9bb9c | 468 | $expectedDate = $this->getYearAndMonthFromOffset(4); |
d486b01e PN |
469 | // check membership end date. |
470 | foreach ([$membershipId1, $membershipId2] as $mId) { | |
471 | $endDate = $this->callAPISuccessGetValue('Membership', [ | |
472 | 'id' => $mId, | |
473 | 'return' => 'end_date', | |
474 | ]); | |
e9b9bb9c | 475 | $this->assertEquals("{$expectedDate['year']}-{$expectedDate['month']}-27", $endDate, ts('End date incorrect.')); |
d486b01e PN |
476 | } |
477 | ||
478 | // At this moment Contact 2 is deceased, but we wait until payment is recorded in civi before marking the contact deceased. | |
479 | // At payment Gateway we update the amount from 150 to 100 | |
480 | // IPN is recorded for subsequent payment (5th payment). | |
481 | $contribution = $this->callAPISuccess('Contribution', 'repeattransaction', [ | |
482 | 'original_contribution_id' => $contributionId, | |
483 | 'contribution_status_id' => 'Completed', | |
484 | 'total_amount' => '100', | |
485 | ]); | |
486 | ||
487 | // now we mark the contact2 as deceased. | |
488 | $this->callAPISuccess('Contact', 'create', [ | |
489 | 'id' => $contactId2, | |
490 | 'is_deceased' => 1, | |
491 | ]); | |
492 | ||
d486b01e PN |
493 | // set membership recurring to null. |
494 | $this->callAPISuccess('Membership', 'create', [ | |
495 | 'id' => $membershipId2, | |
496 | 'contribution_recur_id' => NULL, | |
497 | ]); | |
498 | ||
a33b79a2 EM |
499 | $this->callAPISuccess('Contribution', 'delete', ['id' => $contribution['id']]); |
500 | unset($params['line_items'][1]); | |
501 | $params['total_amount'] = 100; | |
502 | $params['line_items'][0]['params']['id'] = $membershipId1; | |
503 | $params['api.Payment.create']['total_amount'] = 100; | |
504 | ||
505 | $order = $this->callAPISuccess('Order', 'create', $params); | |
506 | ||
d486b01e PN |
507 | // check line item and membership payment count. |
508 | $this->validateAllCounts($membershipId1, 5); | |
509 | $this->validateAllCounts($membershipId2, 4); | |
510 | ||
511 | $checkAgainst = $this->callAPISuccessGetSingle('Membership', [ | |
512 | 'id' => $membershipId2, | |
513 | 'return' => ['end_date', 'status_id'], | |
514 | ]); | |
515 | ||
516 | // record next subsequent payment (6th payment). | |
517 | $this->callAPISuccess('Contribution', 'repeattransaction', [ | |
a33b79a2 | 518 | 'original_contribution_id' => $order['id'], |
d486b01e PN |
519 | 'contribution_status_id' => 'Completed', |
520 | 'total_amount' => '100', | |
521 | ]); | |
522 | ||
523 | // check membership id 1 is renewed | |
524 | $endDate = $this->callAPISuccessGetValue('Membership', [ | |
525 | 'id' => $membershipId1, | |
526 | 'return' => 'end_date', | |
527 | ]); | |
e9b9bb9c | 528 | $expectedDate = $this->getYearAndMonthFromOffset(6); |
529 | $this->assertEquals("{$expectedDate['year']}-{$expectedDate['month']}-27", $endDate, ts('End date incorrect.')); | |
d486b01e PN |
530 | // check line item and membership payment count. |
531 | $this->validateAllCounts($membershipId1, 6); | |
532 | $this->validateAllCounts($membershipId2, 4); | |
533 | ||
534 | // check if membership status and end date is not changed. | |
535 | $membership2 = $this->callAPISuccessGetSingle('Membership', [ | |
536 | 'id' => $membershipId2, | |
537 | 'return' => ['end_date', 'status_id'], | |
538 | ]); | |
539 | $this->assertSame($membership2, $checkAgainst); | |
540 | } | |
541 | ||
542 | /** | |
543 | * Check line item and membership payment count. | |
544 | * | |
545 | * @param int $membershipId | |
546 | * @param int $count | |
547 | * | |
548 | * @throws \CRM_Core_Exception | |
549 | */ | |
ae16be86 | 550 | public function validateAllCounts(int $membershipId, int $count): void { |
d486b01e PN |
551 | $memPayParams = [ |
552 | 'membership_id' => $membershipId, | |
553 | ]; | |
554 | $lineItemParams = [ | |
555 | 'entity_id' => $membershipId, | |
556 | 'entity_table' => 'civicrm_membership', | |
a33b79a2 | 557 | 'contribution_id' => ['>' => 0], |
d486b01e PN |
558 | ]; |
559 | $this->callAPISuccessGetCount('LineItem', $lineItemParams, $count); | |
560 | $this->callAPISuccessGetCount('MembershipPayment', $memPayParams, $count); | |
561 | } | |
562 | ||
e9b9bb9c | 563 | /** |
564 | * Given a number of months offset, get the year and month. | |
565 | * Note the way php arithmetic works, using strtotime('+x months') doesn't | |
566 | * work because it will roll over the day accounting for different number | |
567 | * of days in the month, but we want the same day of the month, x months | |
568 | * from now. | |
569 | * e.g. July 31 + 4 months will return Dec 1 if using php functions, but | |
570 | * we want Nov 31. | |
571 | * | |
572 | * @param int $offset | |
ae16be86 | 573 | * @param int|null $year Optional input year to start |
574 | * @param int|null $month Optional input month to start | |
e9b9bb9c | 575 | * |
576 | * @return array | |
577 | * ['year' => int, 'month' => int] | |
578 | */ | |
ae16be86 | 579 | private function getYearAndMonthFromOffset(int $offset, int $year = NULL, int $month = NULL): array { |
e9b9bb9c | 580 | $dateInfo = [ |
ae16be86 | 581 | 'year' => $year ?? (int) date('Y'), |
582 | 'month' => ($month ?? (int) date('m')) + $offset, | |
e9b9bb9c | 583 | ]; |
584 | if ($dateInfo['month'] > 12) { | |
585 | $dateInfo['year']++; | |
586 | $dateInfo['month'] -= 12; | |
587 | } | |
588 | if ($dateInfo['month'] < 10) { | |
589 | $dateInfo['month'] = "0{$dateInfo['month']}"; | |
590 | } | |
591 | ||
592 | return $dateInfo; | |
593 | } | |
594 | ||
595 | /** | |
596 | * Test getYearAndMonthFromOffset | |
ae16be86 | 597 | * |
e9b9bb9c | 598 | * @dataProvider yearMonthProvider |
599 | * | |
600 | * @param array $input | |
601 | * @param array $expected | |
602 | */ | |
ae16be86 | 603 | public function testGetYearAndMonthFromOffset(array $input, array $expected): void { |
e9b9bb9c | 604 | $this->assertEquals($expected, $this->getYearAndMonthFromOffset($input[0], $input[1], $input[2])); |
605 | } | |
606 | ||
607 | /** | |
608 | * data provider for testGetYearAndMonthFromOffset | |
609 | */ | |
ae16be86 | 610 | public function yearMonthProvider(): array { |
e9b9bb9c | 611 | return [ |
612 | // input = offset, year, current month | |
613 | ['input' => [4, 2020, 1], 'output' => ['year' => '2020', 'month' => '05']], | |
614 | ['input' => [6, 2020, 1], 'output' => ['year' => '2020', 'month' => '07']], | |
615 | ['input' => [4, 2020, 2], 'output' => ['year' => '2020', 'month' => '06']], | |
616 | ['input' => [6, 2020, 2], 'output' => ['year' => '2020', 'month' => '08']], | |
617 | ['input' => [4, 2020, 3], 'output' => ['year' => '2020', 'month' => '07']], | |
618 | ['input' => [6, 2020, 3], 'output' => ['year' => '2020', 'month' => '09']], | |
619 | ['input' => [4, 2020, 4], 'output' => ['year' => '2020', 'month' => '08']], | |
620 | ['input' => [6, 2020, 4], 'output' => ['year' => '2020', 'month' => '10']], | |
621 | ['input' => [4, 2020, 5], 'output' => ['year' => '2020', 'month' => '09']], | |
622 | ['input' => [6, 2020, 5], 'output' => ['year' => '2020', 'month' => '11']], | |
623 | ['input' => [4, 2020, 6], 'output' => ['year' => '2020', 'month' => '10']], | |
624 | ['input' => [6, 2020, 6], 'output' => ['year' => '2020', 'month' => '12']], | |
625 | ['input' => [4, 2020, 7], 'output' => ['year' => '2020', 'month' => '11']], | |
626 | ['input' => [6, 2020, 7], 'output' => ['year' => '2021', 'month' => '01']], | |
627 | ['input' => [4, 2020, 8], 'output' => ['year' => '2020', 'month' => '12']], | |
628 | ['input' => [6, 2020, 8], 'output' => ['year' => '2021', 'month' => '02']], | |
629 | ['input' => [4, 2020, 9], 'output' => ['year' => '2021', 'month' => '01']], | |
630 | ['input' => [6, 2020, 9], 'output' => ['year' => '2021', 'month' => '03']], | |
631 | ['input' => [4, 2020, 10], 'output' => ['year' => '2021', 'month' => '02']], | |
632 | ['input' => [6, 2020, 10], 'output' => ['year' => '2021', 'month' => '04']], | |
633 | ['input' => [4, 2020, 11], 'output' => ['year' => '2021', 'month' => '03']], | |
634 | ['input' => [6, 2020, 11], 'output' => ['year' => '2021', 'month' => '05']], | |
635 | ['input' => [4, 2020, 12], 'output' => ['year' => '2021', 'month' => '04']], | |
636 | ['input' => [6, 2020, 12], 'output' => ['year' => '2021', 'month' => '06']], | |
637 | ]; | |
638 | } | |
639 | ||
eba1a1d7 SP |
640 | /** |
641 | * Test Recurring Contribution Email Receipt Flag | |
642 | * | |
643 | * @throws \CRM_Core_Exception | |
644 | */ | |
ae16be86 | 645 | public function testContributionEmailReceipt(): void { |
eba1a1d7 SP |
646 | $createParams = $this->_params; |
647 | unset($createParams['trxn_id'], $createParams['invoice_id']); | |
648 | ||
649 | // pass null value to is_email_receipt | |
650 | $createParams['is_email_receipt'] = NULL; | |
ae16be86 | 651 | $recurring1 = $this->callAPISuccess('ContributionRecur', 'create', $createParams); |
652 | $recurring1Get = $this->callAPISuccess('ContributionRecur', 'getsingle', ['id' => $recurring1['id']]); | |
eba1a1d7 SP |
653 | // default is_email_receipt column value is 1 |
654 | $this->assertEquals('1', $recurring1Get['is_email_receipt']); | |
655 | ||
656 | // pass empty value to is_email_receipt | |
657 | $createParams['is_email_receipt'] = ''; | |
ae16be86 | 658 | $recurring2 = $this->callAPISuccess('ContributionRecur', 'create', $createParams); |
659 | $recurring2 = ContributionRecur::get(FALSE)->addWhere('id', '=', $recurring2['id'])->addSelect('is_email_receipt')->execute()->first(); | |
660 | $this->assertEquals(NULL, $recurring2['is_email_receipt']); | |
eba1a1d7 | 661 | |
ae16be86 | 662 | // Pass 0 value to is_email_receipt. |
eba1a1d7 | 663 | $createParams['is_email_receipt'] = 0; |
ae16be86 | 664 | $recurring3 = $this->callAPISuccess('ContributionRecur', 'create', $createParams); |
665 | $recurring3Get = $this->callAPISuccess('ContributionRecur', 'getsingle', ['id' => $recurring3['id']]); | |
eba1a1d7 SP |
666 | $this->assertEquals('0', $recurring3Get['is_email_receipt']); |
667 | } | |
668 | ||
bbf58b03 | 669 | } |