Merge pull request #20729 from demeritcowboy/assign-cleanup
[civicrm-core.git] / tests / phpunit / CRM / Contribute / BAO / ContributionRecurTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12 use Civi\Api4\ContributionRecur;
13
14 /**
15 * Class CRM_Contribute_BAO_ContributionRecurTest
16 * @group headless
17 */
18 class CRM_Contribute_BAO_ContributionRecurTest extends CiviUnitTestCase {
19
20 use CRMTraits_Financial_OrderTrait;
21
22 protected $isValidateFinancialsOnPostAssert = TRUE;
23
24 /**
25 * Set up for test.
26 *
27 * @throws \CiviCRM_API3_Exception
28 */
29 public function setUp(): void {
30 parent::setUp();
31 $this->ids['payment_processor'] = $this->paymentProcessorCreate();
32 $this->_params = [
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',
54 'payment_processor_id' => $this->ids['payment_processor'],
55 'is_email_receipt' => 1,
56 'financial_type_id' => 1,
57 'payment_instrument_id' => 1,
58 'campaign_id' => NULL,
59 ];
60 }
61
62 /**
63 * Cleanup after test.
64 *
65 * @throws \CRM_Core_Exception
66 */
67 public function teardown():void {
68 $this->quickCleanUpFinancialEntities();
69 }
70
71 /**
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
75 *
76 * @throws \CRM_Core_Exception
77 */
78 public function testFindSave(): void {
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 /**
88 * Test cancellation works per CRM-14986.
89 *
90 * We are checking for absence of error.
91 *
92 * @throws \CRM_Core_Exception
93 */
94 public function testCancelRecur(): void {
95 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params);
96 CRM_Contribute_BAO_ContributionRecur::cancelRecurContribution(['id' => $contributionRecur['id']]);
97 }
98
99 /**
100 * Test checking if contribution recur object can allow for changes to financial types.
101 *
102 * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
103 */
104 public function testSupportFinancialTypeChange(): void {
105 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params);
106 $this->callAPISuccess('Contribution', 'create', [
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,
114 'receive_date' => 'yesterday',
115 ]);
116 $this->assertTrue(CRM_Contribute_BAO_ContributionRecur::supportsFinancialTypeChange($contributionRecur['id']));
117 }
118
119 /**
120 * Test we don't change unintended fields on API edit
121 *
122 * @throws \CRM_Core_Exception
123 */
124 public function testUpdateRecur(): void {
125 $createParams = $this->_params;
126 $createParams['currency'] = 'XAU';
127 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $createParams);
128 $editParams = [
129 'id' => $contributionRecur['id'],
130 'end_date' => '+ 4 weeks',
131 ];
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
139 /**
140 * Check test contributions aren't picked up as template for non-test recurs
141 *
142 * @throws \API_Exception
143 * @throws \CRM_Core_Exception
144 * @throws \CiviCRM_API3_Exception
145 * @throws \Civi\API\Exception\UnauthorizedException
146 */
147 public function testGetTemplateContributionMatchTest1(): void {
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 *
179 * @throws \API_Exception
180 * @throws \CRM_Core_Exception
181 * @throws \CiviCRM_API3_Exception
182 * @throws \Civi\API\Exception\UnauthorizedException
183 */
184 public function testGetTemplateContributionMatchTest(): void {
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
217 /**
218 * Test that is_template contribution is used where available
219 *
220 * @throws \API_Exception
221 * @throws \CRM_Core_Exception
222 * @throws \CiviCRM_API3_Exception
223 * @throws \Civi\API\Exception\UnauthorizedException
224 */
225 public function testGetTemplateContributionNewTemplate(): void {
226 $contributionRecur = $this->callAPISuccess('contribution_recur', 'create', $this->_params);
227 // Create the template
228 $templateContrib = $this->callAPISuccess('Contribution', 'create', [
229 'contribution_recur_id' => $contributionRecur['id'],
230 'total_amount' => '3.00',
231 'financial_type_id' => 1,
232 'source' => 'Template Contribution',
233 'payment_instrument_id' => 1,
234 'currency' => 'AUD',
235 'contact_id' => $this->individualCreate(),
236 'contribution_status_id' => 1,
237 'receive_date' => 'yesterday',
238 'is_template' => 1,
239 ]);
240 // Create another normal contrib
241 $this->callAPISuccess('Contribution', 'create', [
242 'contribution_recur_id' => $contributionRecur['id'],
243 'total_amount' => '3.00',
244 'financial_type_id' => 1,
245 'source' => 'Non-template Contribution',
246 'payment_instrument_id' => 1,
247 'currency' => 'USD',
248 'contact_id' => $this->individualCreate(),
249 'contribution_status_id' => 1,
250 'receive_date' => 'yesterday',
251 ]);
252 $fetchedTemplate = CRM_Contribute_BAO_ContributionRecur::getTemplateContribution($contributionRecur['id']);
253 // Fetched template should be the is_template, not the latest contrib
254 $this->assertEquals($fetchedTemplate['id'], $templateContrib['id']);
255
256 $repeatContribution = $this->callAPISuccess('Contribution', 'repeattransaction', [
257 'contribution_status_id' => 'Completed',
258 'contribution_recur_id' => $contributionRecur['id'],
259 ]);
260 $this->assertEquals('Template Contribution', $repeatContribution['values'][$repeatContribution['id']]['source']);
261 $this->assertEquals('AUD', $repeatContribution['values'][$repeatContribution['id']]['currency']);
262 }
263
264 /**
265 * Test to check if correct membership is auto renewed.
266 *
267 * @throws \CRM_Core_Exception|\CiviCRM_API3_Exception
268 */
269 public function testAutoRenewalWhenOneMemberIsDeceased(): void {
270 $contactId1 = $this->individualCreate();
271 $contactId2 = $this->individualCreate();
272 $membershipOrganizationId = $this->organizationCreate();
273
274 $this->createExtraneousContribution();
275 $this->callAPISuccess('Contribution', 'create', [
276 'contact_id' => $contactId1,
277 'receive_date' => '2010-01-20',
278 'financial_type_id' => 'Member Dues',
279 'contribution_status_id' => 'Completed',
280 'total_amount' => 150,
281 ]);
282
283 // create membership type
284 $membershipTypeId1 = (int) $this->callAPISuccess('MembershipType', 'create', [
285 'domain_id' => 1,
286 'member_of_contact_id' => $membershipOrganizationId,
287 'financial_type_id' => 'Member Dues',
288 'duration_unit' => 'month',
289 'duration_interval' => 1,
290 'period_type' => 'rolling',
291 'minimum_fee' => 100,
292 'name' => 'Parent',
293 ])['id'];
294
295 $membershipTypeID = (int) $this->callAPISuccess('MembershipType', 'create', [
296 'domain_id' => 1,
297 'member_of_contact_id' => $membershipOrganizationId,
298 'financial_type_id' => 'Member Dues',
299 'duration_unit' => 'month',
300 'duration_interval' => 1,
301 'period_type' => 'rolling',
302 'minimum_fee' => 50,
303 'name' => 'Child',
304 ])['id'];
305
306 $contactIDs = [
307 $contactId1 => $membershipTypeId1,
308 $contactId2 => $membershipTypeID,
309 ];
310
311 $contributionRecurId = $this->callAPISuccess('contribution_recur', 'create', $this->_params)['id'];
312
313 $priceFields = CRM_Price_BAO_PriceSet::getDefaultPriceSet('membership');
314
315 // prepare order api params.
316 $params = [
317 'contact_id' => $contactId1,
318 'receive_date' => '2010-01-20',
319 'financial_type_id' => 'Member Dues',
320 'contribution_recur_id' => $contributionRecurId,
321 'total_amount' => 150,
322 'api.Payment.create' => ['total_amount' => 150],
323 ];
324
325 foreach ($priceFields as $priceField) {
326 $lineItems = [];
327 $contactId = array_search((int) $priceField['membership_type_id'], $contactIDs, TRUE);
328 $lineItems[1] = [
329 'price_field_id' => $priceField['priceFieldID'],
330 'price_field_value_id' => $priceField['priceFieldValueID'],
331 'label' => $priceField['label'],
332 'field_title' => $priceField['label'],
333 'qty' => 1,
334 'unit_price' => $priceField['amount'],
335 'line_total' => $priceField['amount'],
336 'financial_type_id' => $priceField['financial_type_id'],
337 'entity_table' => 'civicrm_membership',
338 'membership_type_id' => $priceField['membership_type_id'],
339 ];
340 $params['line_items'][] = [
341 'line_item' => $lineItems,
342 'params' => [
343 'contact_id' => $contactId,
344 'membership_type_id' => $priceField['membership_type_id'],
345 'source' => 'Payment',
346 'join_date' => date('Y-m', strtotime('1 month ago')) . '-28',
347 'start_date' => date('Y-m') . '-28',
348 'contribution_recur_id' => $contributionRecurId,
349 'status_id' => 'Pending',
350 'is_override' => 1,
351 ],
352 ];
353 }
354 $order = $this->callAPISuccess('Order', 'create', $params);
355 $contributionId = $order['id'];
356 $membershipId1 = $this->callAPISuccessGetValue('Membership', [
357 'contact_id' => $contactId1,
358 'membership_type_id' => $membershipTypeId1,
359 'return' => 'id',
360 ]);
361
362 $membershipId2 = $this->callAPISuccessGetValue('Membership', [
363 'contact_id' => $contactId2,
364 'membership_type_id' => $membershipTypeID,
365 'return' => 'id',
366 ]);
367
368 // First renewal (2nd payment).
369 $this->callAPISuccess('Contribution', 'repeattransaction', [
370 'original_contribution_id' => $contributionId,
371 'contribution_status_id' => 'Completed',
372 ]);
373
374 // Second Renewal (3rd payment).
375 $this->callAPISuccess('Contribution', 'repeattransaction', [
376 'original_contribution_id' => $contributionId,
377 'contribution_status_id' => 'Completed',
378 ]);
379
380 // Third renewal (4th payment).
381 $this->callAPISuccess('Contribution', 'repeattransaction', ['original_contribution_id' => $contributionId, 'contribution_status_id' => 'Completed']);
382
383 // check line item and membership payment count.
384 $this->validateAllCounts($membershipId1, 4);
385 $this->validateAllCounts($membershipId2, 4);
386
387 $expectedDate = $this->getYearAndMonthFromOffset(4);
388 // check membership end date.
389 foreach ([$membershipId1, $membershipId2] as $mId) {
390 $endDate = $this->callAPISuccessGetValue('Membership', [
391 'id' => $mId,
392 'return' => 'end_date',
393 ]);
394 $this->assertEquals("{$expectedDate['year']}-{$expectedDate['month']}-27", $endDate, ts('End date incorrect.'));
395 }
396
397 // At this moment Contact 2 is deceased, but we wait until payment is recorded in civi before marking the contact deceased.
398 // At payment Gateway we update the amount from 150 to 100
399 // IPN is recorded for subsequent payment (5th payment).
400 $contribution = $this->callAPISuccess('Contribution', 'repeattransaction', [
401 'original_contribution_id' => $contributionId,
402 'contribution_status_id' => 'Completed',
403 'total_amount' => '100',
404 ]);
405
406 // now we mark the contact2 as deceased.
407 $this->callAPISuccess('Contact', 'create', [
408 'id' => $contactId2,
409 'is_deceased' => 1,
410 ]);
411
412 // set membership recurring to null.
413 $this->callAPISuccess('Membership', 'create', [
414 'id' => $membershipId2,
415 'contribution_recur_id' => NULL,
416 ]);
417
418 $this->callAPISuccess('Contribution', 'delete', ['id' => $contribution['id']]);
419 unset($params['line_items'][1]);
420 $params['total_amount'] = 100;
421 $params['line_items'][0]['params']['id'] = $membershipId1;
422 $params['api.Payment.create']['total_amount'] = 100;
423
424 $order = $this->callAPISuccess('Order', 'create', $params);
425
426 // check line item and membership payment count.
427 $this->validateAllCounts($membershipId1, 5);
428 $this->validateAllCounts($membershipId2, 4);
429
430 $checkAgainst = $this->callAPISuccessGetSingle('Membership', [
431 'id' => $membershipId2,
432 'return' => ['end_date', 'status_id'],
433 ]);
434
435 // record next subsequent payment (6th payment).
436 $this->callAPISuccess('Contribution', 'repeattransaction', [
437 'original_contribution_id' => $order['id'],
438 'contribution_status_id' => 'Completed',
439 'total_amount' => '100',
440 ]);
441
442 // check membership id 1 is renewed
443 $endDate = $this->callAPISuccessGetValue('Membership', [
444 'id' => $membershipId1,
445 'return' => 'end_date',
446 ]);
447 $expectedDate = $this->getYearAndMonthFromOffset(6);
448 $this->assertEquals("{$expectedDate['year']}-{$expectedDate['month']}-27", $endDate, ts('End date incorrect.'));
449 // check line item and membership payment count.
450 $this->validateAllCounts($membershipId1, 6);
451 $this->validateAllCounts($membershipId2, 4);
452
453 // check if membership status and end date is not changed.
454 $membership2 = $this->callAPISuccessGetSingle('Membership', [
455 'id' => $membershipId2,
456 'return' => ['end_date', 'status_id'],
457 ]);
458 $this->assertSame($membership2, $checkAgainst);
459 }
460
461 /**
462 * Check line item and membership payment count.
463 *
464 * @param int $membershipId
465 * @param int $count
466 *
467 * @throws \CRM_Core_Exception
468 */
469 public function validateAllCounts(int $membershipId, int $count): void {
470 $memPayParams = [
471 'membership_id' => $membershipId,
472 ];
473 $lineItemParams = [
474 'entity_id' => $membershipId,
475 'entity_table' => 'civicrm_membership',
476 'contribution_id' => ['>' => 0],
477 ];
478 $this->callAPISuccessGetCount('LineItem', $lineItemParams, $count);
479 $this->callAPISuccessGetCount('MembershipPayment', $memPayParams, $count);
480 }
481
482 /**
483 * Given a number of months offset, get the year and month.
484 * Note the way php arithmetic works, using strtotime('+x months') doesn't
485 * work because it will roll over the day accounting for different number
486 * of days in the month, but we want the same day of the month, x months
487 * from now.
488 * e.g. July 31 + 4 months will return Dec 1 if using php functions, but
489 * we want Nov 31.
490 *
491 * @param int $offset
492 * @param int|null $year Optional input year to start
493 * @param int|null $month Optional input month to start
494 *
495 * @return array
496 * ['year' => int, 'month' => int]
497 */
498 private function getYearAndMonthFromOffset(int $offset, int $year = NULL, int $month = NULL): array {
499 $dateInfo = [
500 'year' => $year ?? (int) date('Y'),
501 'month' => ($month ?? (int) date('m')) + $offset,
502 ];
503 if ($dateInfo['month'] > 12) {
504 $dateInfo['year']++;
505 $dateInfo['month'] -= 12;
506 }
507 if ($dateInfo['month'] < 10) {
508 $dateInfo['month'] = "0{$dateInfo['month']}";
509 }
510
511 return $dateInfo;
512 }
513
514 /**
515 * Test getYearAndMonthFromOffset
516 *
517 * @dataProvider yearMonthProvider
518 *
519 * @param array $input
520 * @param array $expected
521 */
522 public function testGetYearAndMonthFromOffset(array $input, array $expected): void {
523 $this->assertEquals($expected, $this->getYearAndMonthFromOffset($input[0], $input[1], $input[2]));
524 }
525
526 /**
527 * data provider for testGetYearAndMonthFromOffset
528 */
529 public function yearMonthProvider(): array {
530 return [
531 // input = offset, year, current month
532 ['input' => [4, 2020, 1], 'output' => ['year' => '2020', 'month' => '05']],
533 ['input' => [6, 2020, 1], 'output' => ['year' => '2020', 'month' => '07']],
534 ['input' => [4, 2020, 2], 'output' => ['year' => '2020', 'month' => '06']],
535 ['input' => [6, 2020, 2], 'output' => ['year' => '2020', 'month' => '08']],
536 ['input' => [4, 2020, 3], 'output' => ['year' => '2020', 'month' => '07']],
537 ['input' => [6, 2020, 3], 'output' => ['year' => '2020', 'month' => '09']],
538 ['input' => [4, 2020, 4], 'output' => ['year' => '2020', 'month' => '08']],
539 ['input' => [6, 2020, 4], 'output' => ['year' => '2020', 'month' => '10']],
540 ['input' => [4, 2020, 5], 'output' => ['year' => '2020', 'month' => '09']],
541 ['input' => [6, 2020, 5], 'output' => ['year' => '2020', 'month' => '11']],
542 ['input' => [4, 2020, 6], 'output' => ['year' => '2020', 'month' => '10']],
543 ['input' => [6, 2020, 6], 'output' => ['year' => '2020', 'month' => '12']],
544 ['input' => [4, 2020, 7], 'output' => ['year' => '2020', 'month' => '11']],
545 ['input' => [6, 2020, 7], 'output' => ['year' => '2021', 'month' => '01']],
546 ['input' => [4, 2020, 8], 'output' => ['year' => '2020', 'month' => '12']],
547 ['input' => [6, 2020, 8], 'output' => ['year' => '2021', 'month' => '02']],
548 ['input' => [4, 2020, 9], 'output' => ['year' => '2021', 'month' => '01']],
549 ['input' => [6, 2020, 9], 'output' => ['year' => '2021', 'month' => '03']],
550 ['input' => [4, 2020, 10], 'output' => ['year' => '2021', 'month' => '02']],
551 ['input' => [6, 2020, 10], 'output' => ['year' => '2021', 'month' => '04']],
552 ['input' => [4, 2020, 11], 'output' => ['year' => '2021', 'month' => '03']],
553 ['input' => [6, 2020, 11], 'output' => ['year' => '2021', 'month' => '05']],
554 ['input' => [4, 2020, 12], 'output' => ['year' => '2021', 'month' => '04']],
555 ['input' => [6, 2020, 12], 'output' => ['year' => '2021', 'month' => '06']],
556 ];
557 }
558
559 /**
560 * Test Recurring Contribution Email Receipt Flag
561 *
562 * @throws \CRM_Core_Exception
563 */
564 public function testContributionEmailReceipt(): void {
565 $createParams = $this->_params;
566 unset($createParams['trxn_id'], $createParams['invoice_id']);
567
568 // pass null value to is_email_receipt
569 $createParams['is_email_receipt'] = NULL;
570 $recurring1 = $this->callAPISuccess('ContributionRecur', 'create', $createParams);
571 $recurring1Get = $this->callAPISuccess('ContributionRecur', 'getsingle', ['id' => $recurring1['id']]);
572 // default is_email_receipt column value is 1
573 $this->assertEquals('1', $recurring1Get['is_email_receipt']);
574
575 // pass empty value to is_email_receipt
576 $createParams['is_email_receipt'] = '';
577 $recurring2 = $this->callAPISuccess('ContributionRecur', 'create', $createParams);
578 $recurring2 = ContributionRecur::get(FALSE)->addWhere('id', '=', $recurring2['id'])->addSelect('is_email_receipt')->execute()->first();
579 $this->assertEquals(NULL, $recurring2['is_email_receipt']);
580
581 // Pass 0 value to is_email_receipt.
582 $createParams['is_email_receipt'] = 0;
583 $recurring3 = $this->callAPISuccess('ContributionRecur', 'create', $createParams);
584 $recurring3Get = $this->callAPISuccess('ContributionRecur', 'getsingle', ['id' => $recurring3['id']]);
585 $this->assertEquals('0', $recurring3Get['is_email_receipt']);
586 }
587
588 }