· 4 years ago · Jan 29, 2021, 06:44 PM
1<?php
2
3declare(strict_types=1);
4
5namespace Maduro\Model;
6
7use DateInterval;
8use Datetime;
9use Maduro\Exception\PaymentException;
10use Maduro\Exception\DbException;
11use Maduro\Exception\ValidationException;
12use Maduro\Utils\Validator as v;
13
14/**
15 * This class implements everything used in payments
16 */
17class PaymentModel extends BaseModel
18{
19 /**
20 * Returns info about a payment
21 * @param int $payment_id
22 * @throws dbException
23 * @return array
24 */
25 public function _readPayment(int $payment_id): ?array
26 {
27 $columns = [
28 "id",
29 "title",
30 "user_id",
31 "payment_type",
32 "value",
33 "status",
34 "custom_member_type",
35 "old_member_type",
36 "new_member_type",
37 "pagseguro_id",
38 "created_at",
39 "updated_at"
40 ];
41 return $this->dbConnection->Read("payments", $columns, "id", $payment_id);
42 }
43
44 /**
45 * Returns the payments from an user -- no search, get only
46 *
47 * Returns an associative array with the user payments
48 *
49 * @param array $data arguments for search model
50 * @param int $user_id the id to search for
51 * @throws DbException
52 * @return array
53 */
54 public function getPaymentsFromUser($data, int $user_id, $locale): array
55 {
56 $search_term = null;
57 $filters = null;
58 $page = null;
59 $per_page = null;
60 $order_by = null;
61 $order_direction = null;
62
63 if (isset($data['search_term'])) {
64 $search_term = $data['search_term'];
65 unset($data['search_term']);
66 }
67
68 if (isset($data['operator'])) {
69 $operator = $data['operator'];
70 unset($data['operator']);
71 } else {
72 $operator = "AND";
73 }
74
75 if (isset($data['page'])) {
76 $page = (int) $data['page'];
77 unset($data['page']);
78 }
79
80 if (isset($data['per_page'])) {
81 $per_page = (int) $data['per_page'];
82 unset($data['per_page']);
83 }
84
85 if (isset($data['order_by'])) {
86 $order_by = $data['order_by'];
87 unset($data['order_by']);
88 }
89
90 if (isset($data['order_direction'])) {
91 $order_direction = $data['order_direction'];
92 unset($data['order_direction']);
93 }
94
95 if (!empty($data)) {
96 $base_filters = $data;
97 foreach ($base_filters as $key=>$filter) {
98 $filter = (string) $filter;
99 $filters[$key] = explode(",", $filter);
100 }
101 }
102 return $this->_searchPayments(
103 $search_term,
104 $filters,
105 $operator,
106 $page,
107 $per_page,
108 $order_by,
109 $order_direction,
110 $locale,
111 $user_id
112 );
113 }
114
115 /**
116 * Get payments from all users
117 *
118 * @param array $data
119 * @return array
120 * @throws DbException
121 */
122 public function getAllPayments($data, $locale): array
123 {
124 $search_term = null;
125 $filters = null;
126 $page = null;
127 $per_page = null;
128 $order_by = null;
129 $order_direction = null;
130
131 if (isset($data['search_term'])) {
132 $search_term = $data['search_term'];
133 unset($data['search_term']);
134 }
135
136 if (isset($data['operator'])) {
137 $operator = $data['operator'];
138 unset($data['operator']);
139 } else {
140 $operator = "AND";
141 }
142
143 if (isset($data['page'])) {
144 $page = (int) $data['page'];
145 unset($data['page']);
146 }
147
148 if (isset($data['per_page'])) {
149 $per_page = (int) $data['per_page'];
150 unset($data['per_page']);
151 }
152
153 if (isset($data['order_by'])) {
154 $order_by = $data['order_by'];
155 unset($data['order_by']);
156 }
157 if (isset($data['order_direction'])) {
158 $order_direction = $data['order_direction'];
159 unset($data['order_direction']);
160 }
161
162 if (!empty($data)) {
163 $base_filters = $data;
164 foreach ($base_filters as $key=>$filter) {
165 $filter = (string) $filter;
166 $filters[$key] = explode(",", $filter);
167 }
168 }
169
170 return $this->_searchPayments(
171 $search_term,
172 $filters,
173 $operator,
174 $page,
175 $per_page,
176 $order_by,
177 $order_direction,
178 $locale
179 );
180 }
181
182 /**
183 * Get payments from all users
184 *
185 * @param array $data
186 * @return array
187 * @throws DbException
188 */
189 public function getMagazinePayments($data, $locale): array
190 {
191 $search_term = null;
192 $filters = null;
193 $page = null;
194 $per_page = null;
195 $order_by = null;
196 $order_direction = null;
197
198
199 if (isset($data['search_term'])) {
200 $search_term = $data['search_term'];
201 unset($data['search_term']);
202 }
203
204 if (isset($data['operator'])) {
205 unset($data['operator']);
206 }
207
208 if (isset($data['page'])) {
209 $page = (int) $data['page'];
210 unset($data['page']);
211 }
212
213 if (isset($data['per_page'])) {
214 $per_page = (int) $data['per_page'];
215 unset($data['per_page']);
216 }
217
218 if (isset($data['order_by'])) {
219 $order_by = $data['order_by'];
220 unset($data['order_by']);
221 }
222 if (isset($data['order_direction'])) {
223 $order_direction = $data['order_direction'];
224 unset($data['order_direction']);
225 }
226
227 $filters = [
228 "p.created_at" =>
229 (object) [
230 "operator"=> ">",
231 "value" => date_create()->sub(new DateInterval('P1Y'))->format('Y-m-01')
232 ],
233 "status" => 5,
234 "optional_payment" => 1
235 ];
236
237 if (!empty($data)) {
238 $base_filters = $data;
239 foreach ($base_filters as $key=>$filter) {
240 $filter = (string) $filter;
241 $filters[$key] = explode(",", $filter);
242 }
243 }
244
245 $recent_payments = $this->_searchPayments(
246 $search_term,
247 $filters,
248 "AND",
249 $page,
250 $per_page,
251 $order_by,
252 $order_direction,
253 $locale
254 );
255
256 $current_year = date_create()->format("Y");
257 $anuity_payments = $this->dbConnection->CustomQuery(
258 "SELECT p.id, p.title, payment_type, optional_payment, value, status, p.updated_at, p.created_at, u.id AS u_id, first_name, last_name, city, lstate, cep, country, street, number, complement
259 FROM payments AS p
260 JOIN users AS u
261 ON p.user_id = u.id
262 JOIN selected_available_payments AS sap
263 ON sap.payment_id = p.id
264 JOIN available_payments AS ap
265 ON sap.available_payment_id = ap.id
266 AND ap.title='$current_year'
267 WHERE status=5
268 AND payment_type=2
269 AND optional_payment=1"
270 );
271
272 $merged = (array_merge($anuity_payments, $recent_payments["results"]));
273
274 $results = [];
275 $user_ids = [];
276
277 foreach($merged as $payment){
278 if(!in_array($payment["u_id"], $user_ids)){
279 array_push($user_ids, $payment["u_id"]);
280 array_push($results, $payment);
281 }
282 }
283
284 return ["results_found" => count($results), "results" => $results];
285 }
286
287 public function _searchPayments(
288 ?string $search_term = null,
289 ?array $filters = null,
290 ?string $operator = "AND",
291 ?int $page = null,
292 ?int $per_page = null,
293 ?string $order_by = null,
294 ?string $order_direction = null,
295 ?string $locale = "en",
296 ?int $user_id = null
297 ): array {
298 $sm = new SearchModel();
299 $sm->setTable("payments");
300 $sm->setColumns(["id", "title", "payment_type", "optional_payment", "value", "status", "updated_at", "created_at", "proof_file"]);
301 $sm->setSearchColumns(["title"]);
302
303 if (!empty($user_id)) {
304 $filters["user_id"] = $user_id;
305 }
306 if (!empty($operator)) {
307 $sm->setOperator($operator);
308 }
309 if (!empty($order_by)) {
310 $sm->setOrderBy($order_by);
311 }
312 if (!empty($search_term)) {
313 $sm->setSearchTerm($search_term);
314 }
315 if (!empty($filters)) {
316 $sm->setFilters($filters);
317 }
318 if (!empty($per_page)) {
319 $sm->setItemsPerPage($per_page);
320 }
321 if (!empty($page)) {
322 $sm->setPage($page);
323 }
324 if (!empty($order_direction)) {
325 $sm->setOrderDirection($order_direction);
326 }
327
328 // Setting join stuff
329 $sm->setJoinTable("users");
330 $sm->setJoinColumns(["id", "first_name", "last_name", "city", "lstate", "cep", "country", "street", "number", "complement"]);
331 $sm->setJoinBase("user_id");
332 $sm->setJoinOther("id");
333 $sm->setJoin(true);
334 $res = $sm->search();
335 foreach ($res["results"] as $key=>$result) {
336 $res["results"][$key]["status_code"] = $result["status"];
337 $res["results"][$key]["status"] = $this->getStatusText($result["status"], $locale);
338 }
339 return $res;
340 }
341
342 public function getAllAvailablePayments($data)
343 {
344 $sm = new SearchModel();
345 $sm->setTable("available_payments");
346 $sm->setColumns(["id", "title", "m_1_price", "m_2_price", "m_3_price", "m_4_price", "m_1_opt_price", "m_2_opt_price", "m_3_opt_price", "m_4_opt_price", "start_date", "end_date"]);
347 $sm->setSearchColumns(["title"]);
348
349 // damn man that sucks
350 $search_term = null;
351 $filters = [];
352 $page = null;
353 $per_page = null;
354 $order_by = null;
355 $order_direction = null;
356
357 if (isset($data['search_term'])) {
358 $search_term = $data['search_term'];
359 unset($data['search_term']);
360 }
361
362 if (isset($data['operator'])) {
363 $operator = $data['operator'];
364 unset($data['operator']);
365 } else {
366 $operator = "AND";
367 }
368
369 if (isset($data['page'])) {
370 $page = (int) $data['page'];
371 unset($data['page']);
372 }
373
374 if (isset($data['per_page'])) {
375 $per_page = (int) $data['per_page'];
376 unset($data['per_page']);
377 }
378
379 if (isset($data['order_by'])) {
380 $order_by = $data['order_by'];
381 unset($data['order_by']);
382 }
383
384 if (isset($data['order_direction'])) {
385 $order_direction = $data['order_direction'];
386 unset($data['order_direction']);
387 }
388
389 if (!empty($data)) {
390 $base_filters = $data;
391 foreach ($base_filters as $key=>$filter) {
392 $filter = (string) $filter;
393 $filters[$key] = explode(",", $filter);
394 }
395 }
396
397 if (!empty($operator)) {
398 $sm->setOperator($operator);
399 }
400 if (!empty($order_by)) {
401 $sm->setOrderBy($order_by);
402 }
403 if (!empty($search_term)) {
404 $sm->setSearchTerm($search_term);
405 }
406 if (!empty($filters)) {
407 $sm->setFilters($filters);
408 }
409 if (!empty($per_page)) {
410 $sm->setItemsPerPage($per_page);
411 }
412 if (!empty($page)) {
413 $sm->setPage($page);
414 }
415 if (!empty($order_direction)) {
416 $sm->setOrderDirection($order_direction);
417 }
418 $res = $sm->search();
419 foreach ($res["results"] as $result) {
420 $new_res[] = [
421 "id" => $result["id"],
422 "title" => $result["title"],
423 "m_1_price" => $result["m_1_price"],
424 "m_2_price" => $result["m_2_price"],
425 "m_3_price" => $result["m_3_price"],
426 "m_4_price" => $result["m_4_price"],
427 "m_1_opt_price" => $result["m_1_opt_price"],
428 "m_2_opt_price" => $result["m_2_opt_price"],
429 "m_3_opt_price" => $result["m_3_opt_price"],
430 "m_4_opt_price" => $result["m_4_opt_price"],
431 "start_date" => v::dateReverse($result["start_date"]),
432 "end_date" => v::dateReverse($result["end_date"])
433 ];
434 }
435 $res["results"] = $new_res;
436 return $res;
437 }
438
439 /**
440 * Returns available payments for a user
441 * @param int $user_id the id for the user
442 * @throws DbException
443 * @return array associative array with the user data
444 */
445 public function getAvailablePaymentsFor(int $user_id): array
446 {
447 // Getting user data
448 $date_format = "Y-m-d H:i:s";
449 $col = ['activated_at', 'created_at', 'filiated_at', 'member_type'];
450 $user = $this->dbConnection->Read('users', $col, 'id', $user_id);
451
452 if (!v::emptyDate($user['activated_at'])) {
453 $activation_date = DateTime::createFromFormat($date_format, $user["activated_at"]);
454 }
455
456 if (!v::emptyDate($user['filiated_at'])) {
457 $filiation_date = DateTime::createFromFormat($date_format, $user["filiated_at"]);
458 } else {
459 $filiation_date = new DateTime();
460 }
461
462 $creation_date = DateTime::createFromFormat($date_format, $user["created_at"]);
463
464
465 // Pick latest of the 3 dates
466 if(!empty($activation_date))
467 $end_date = ($activation_date > $creation_date ?
468 $activation_date : $creation_date);
469 $end_date = (($filiation_date > $end_date) ? $filiation_date : $end_date);
470
471 $end_date_year = date_format($end_date, "Y") . "-1-1"; // 01 / 01 / YEAR
472 $formated_start_date = date_format((new DateTime()), "Y-m-d");
473
474
475 $query = ("SELECT ap.*
476 FROM available_payments AS ap
477 LEFT JOIN (
478 SELECT DISTINCT v1.user_id, v1.available_payment_id
479 FROM (
480 SELECT user_id
481 , available_payment_id
482 , MAX(updated_at) AS upd_at
483 FROM selected_available_payments AS ap
484 JOIN payments AS p
485 ON ap.payment_id = p.id
486 GROUP BY user_id, available_payment_id
487 ) AS v1
488 JOIN payments AS p
489 ON p.updated_at = v1.upd_at
490 AND p.user_id = v1.user_id
491 AND p.status <> 6
492 ) AS pc
493 ON pc.user_id = ?
494 AND ap.id = pc.available_payment_id
495 WHERE pc.user_id IS NULL
496 AND end_date >= ?
497 AND start_date < CURRENT_TIMESTAMP
498 ORDER BY ap.id
499 ");
500
501 $params_in_order = [$user_id, $end_date_year];
502 $results = $this->dbConnection->CustomQuery($query, $params_in_order);
503 $new_results = [];
504 $aspirant_limit = $this->aspirantLimit($user);
505 foreach ($results as $key=>$result) {
506 $date = DateTime::createFromFormat("Y-m-d H:i:s", $result['end_date']);
507 $prices = $this->beautifyAvailablePayment($result, $user['member_type'], $this->canChange($date));
508 if($aspirant_limit && $date>$aspirant_limit){
509 unset($prices["m_1_price"]);
510 }
511 array_push($new_results, $prices);
512 $new_results[$key]["start_date"] = v::dateReverse($new_results[$key]["start_date"]);
513 $new_results[$key]["end_date"] = v::dateReverse($new_results[$key]["end_date"]);
514 }
515 return $new_results;
516 }
517
518 /**
519 * Checks if a user is defaulting
520 * @param int $user_id
521 * @return bool
522 */
523 public function isUserDefaulting(int $user_id): bool
524 {
525 // Getting user data
526 $date_format = "Y-m-d H:i:s";
527 $col = ['activated_at', 'created_at', 'filiated_at', 'member_type'];
528 $user = $this->dbConnection->Read('users', $col, 'id', $user_id);
529 if (!v::emptyDate($user['activated_at'])) {
530 $activation_date = DateTime::createFromFormat($date_format, $user["activated_at"]);
531 }
532 if (!v::emptyDate($user['filiated_at'])) {
533 $filiation_date = DateTime::createFromFormat($date_format, $user["filiated_at"]);
534 } else {
535 $filiation_date = new DateTime();
536 }
537 $creation_date = DateTime::createFromFormat($date_format, $user["created_at"]);
538
539 if ($user["member_type"] == 0) {
540 return false;
541 }
542
543 // Pick latest of the 3 dates
544 $end_date = (empty($activation_date) ? $activation_date : $creation_date);
545 $end_date = (($filiation_date > $end_date) ? $filiation_date : $end_date);
546
547 $end_date_year = date_format($end_date, "Y") . "-1-1"; // 20XX-1-1, I'm sorry for this
548
549 $query = (" SELECT ap.*
550 FROM available_payments AS ap
551 LEFT JOIN (
552 SELECT DISTINCT v1.user_id, v1.available_payment_id
553 FROM (
554 SELECT user_id
555 , available_payment_id
556 , MAX(updated_at) AS upd_at
557 FROM selected_available_payments AS ap
558 JOIN payments AS p
559 ON ap.payment_id = p.id
560 GROUP BY user_id, available_payment_id
561 ) AS v1
562 JOIN payments AS p
563 ON p.updated_at = v1.upd_at
564 AND p.user_id = v1.user_id
565 AND p.status = 5
566 ) AS pc
567 ON pc.user_id = ?
568 AND ap.id = pc.available_payment_id
569 WHERE pc.user_id IS NULL
570 AND end_date >= ?
571 AND end_date < CURRENT_TIMESTAMP
572 AND start_date < CURRENT_TIMESTAMP
573 ORDER BY ap.id
574 ");
575 $params_in_order = [$user_id, $end_date_year];
576 $results = $this->dbConnection->CustomQuery($query, $params_in_order);
577 if (count($results) > 0) {
578 return true;
579 }
580 return false;
581 }
582
583
584 public function updateUserDefaulting(int $user_id)
585 {
586 $req = [
587 "defaulting" => $this->isUserDefaulting($user_id)
588 ];
589 $changed = $this->dbConnection->Update("users", $req, "id", $user_id);
590 if ($changed && $req["defaulting"] == 1) {
591 $mm = new MailingModel();
592 $mm->defaulting($user_id);
593 }
594 }
595
596 /** Endpoint for creating available payments
597 * @param array $data
598 * @throws ValidationException
599 * @throws DbException
600 * @return int
601 */
602 public function createAvailablePayment(array $data)
603 {
604 if (empty($data)) {
605 throw new ValidationException("Empty request", 406);
606 }
607 $title = v::validateTitle($data["title"]);
608 $m_1_price = v::validateMoney($data["m_1_price"]);
609 $m_2_price = v::validateMoney($data["m_2_price"]);
610 $m_3_price = v::validateMoney($data["m_3_price"]);
611 $m_4_price = v::validateMoney($data["m_4_price"]);
612 $m_1_opt_price = v::validateMoney($data["m_1_opt_price"]);
613 $m_2_opt_price = v::validateMoney($data["m_2_opt_price"]);
614 $m_3_opt_price = v::validateMoney($data["m_3_opt_price"]);
615 $m_3_opt_price = v::validateMoney($data["m_4_opt_price"]);
616 $start_date = v::validateDate($data["start_date"]);
617 $end_date = v::validateDate($data["end_date"]);
618
619 return $this->_createAvailablePayment(
620 $title,
621 $m_1_price,
622 $m_2_price,
623 $m_3_price,
624 $m_4_price,
625 $m_1_opt_price,
626 $m_2_opt_price,
627 $m_3_opt_price,
628 $m_4_opt_price,
629 $start_date,
630 $end_date
631 );
632 }
633
634
635 /**
636 * This function actually creates the available payment
637 *
638 * We should probably set a different approach to detect if theres
639 * a similar available_payment
640 *
641 * @param string $name The name of the payment
642 * @param float $m_1_price The price for members of type 1
643 * @param float $m_2_price The price for members of type 2
644 * @param float $m_3_price The price for members of type 3
645 * @param float $m_1_opt_price The optional additional price for members of type 1
646 * @param float $m_2_opt_price The optional additional price for members of type 2
647 * @param float $m_3_opt_price The optional additional price for members of type 3
648 * @throws DbException
649 * @return int
650 */
651 public function _createAvailablePayment(
652 string $title,
653 float $m_1_price,
654 float $m_2_price,
655 float $m_3_price,
656 float $m_4_price,
657 float $m_1_opt_price,
658 float $m_2_opt_price,
659 float $m_3_opt_price,
660 float $m_4_opt_price,
661 string $start_date,
662 string $end_date
663 ): int {
664 $r = $this->dbConnection->ReadAllWhere("available_payments", ["id"], "title", $title);
665 if (count(array_column($r, "title")) > 0) {
666 throw new Exception("There's already a similar anuity, if you're"
667 ." sure you want to create a new one, use a"
668 ." different name");
669 }
670
671 $in = [
672 "title" => $title,
673 "m_1_price" => $m_1_price,
674 "m_2_price" => $m_2_price,
675 "m_3_price" => $m_3_price,
676 "m_4_price" => $m_4_price,
677 "m_1_opt_price" => $m_1_opt_price,
678 "m_2_opt_price" => $m_2_opt_price,
679 "m_3_opt_price" => $m_3_opt_price,
680 "m_4_opt_price" => $m_4_opt_price,
681 "start_date" => $start_date,
682 "end_date" => $end_date
683 ];
684 return $this->dbConnection->Create("available_payments", $in);
685 }
686
687 /**
688 * Endpoint for deleting an available payment
689 *
690 * Checks if the available payment was already paid before deleting
691 * This one has a little overhead but it is rarely used so I guess its ok
692 *
693 * @param array $data
694 * @return void
695 * @throws ValidationException
696 * @throws DbException
697 */
698 public function deleteAvailablePayment(array $data)
699 {
700 $ap_id = v::validateId($data['id']);
701
702 $selected_in = $this->dbConnection->ReadAllWhere(
703 "selected_available_payments",
704 ["payment_id"],
705 "available_payment_id",
706 $ap_id
707 );
708 foreach ($selected_in as $si) {
709 $p = $this->dbConnection->Read("payments", ["status"], "id", $si["payment_id"]);
710 if ($p["status"] != 6) {
711 throw new PaymentException("You can't delete a payment"
712 ." that was already paid!", 401);
713 }
714 }
715
716 $this->_deleteAvailablePayment($ap_id);
717 }
718 /**
719 * Deletes an available payment
720 * @param int $payment_id
721 * @throws DbException
722 * @return void
723 */
724 public function _deleteAvailablePayment(int $payment_id)
725 {
726 $this->dbConnection->Delete('available_payments', 'id', $payment_id);
727 }
728
729
730 /**
731 * Endpoint for creating a payment from a price
732 *
733 * @param $data
734 * @param $user_id
735 * @return int
736 * @throws DbException
737 * @throws PaymentException
738 * @throws ValidationException
739 * @return int
740 */
741 public function createPaymentFromPrice($data, $user_id, $paid): int
742 {
743 if (empty($data)) {
744 throw new ValidationException("Empty request body...", 406);
745 }
746 if (empty($user_id)) {
747 throw new ValidationException("Expecting user", 406);
748 }
749
750 $qty = (empty($data["qty"]) ? 0 : (int) $data["qty"]);
751 $user_id = v::validateId($user_id);
752
753 // Fixing file title for pagseguro purposes
754 $title = $data["article_code"] . ' - ' . $data['article_title'];
755
756 $title = preg_replace(array("/(á|à|ã|â|ä)/","/(Á|À|Ã|Â|Ä)/","/(é|è|ê|ë)/","/(É|È|Ê|Ë)/","/(í|ì|î|ï)/","/(Í|Ì|Î|Ï)/","/(ó|ò|õ|ô|ö)/","/(Ó|Ò|Õ|Ô|Ö)/","/(ú|ù|û|ü)/","/(Ú|Ù|Û|Ü)/","/(ñ)/","/(Ñ)/", "/(ç)/", "/(Ç)/"),explode(" ","a A e E i I o O u U n N c C"), $title);
757
758 if(strlen($title) >= 99)
759 $title = substr($title, 0, 95) . "...";
760
761 $user = $this->dbConnection->Read("users", ["member_type", "defaulting"], "id", $user_id);
762 $member_type = ($user["defaulting"] ? 0 : $user["member_type"]);
763 return $this->_createPaymentFromPrice($user_id, $member_type, $title, $paid, $qty);
764 }
765
766 /**
767 * Creates a payment from a price
768 * @param int $user_id The id of the user that is purchasing
769 * @param int $price_id The id of the price
770 * @param int $member_type The type of the member buying
771 * @param int $qty The quanty
772 * @throws DbException
773 * @throws PaymentException
774 * @return int
775 */
776 public function _createPaymentFromPrice(int $user_id, int $member_type, string $title, bool $paid, int $qty=0): int
777 {
778 $col = ["price_for_m0", "price_for_m1", "price_for_m2", "price_for_m3"];
779 $price = $this->dbConnection->ReadAll('payment_prices', $col, null, null, null);
780 switch ($member_type) {
781 case 0:
782 $key = "price_for_m0";
783 break;
784 case 1:
785 $key = "price_for_m1";
786 break;
787 case 2:
788 $key = "price_for_m2";
789 break;
790 case 3:
791 $key = "price_for_m3";
792 break;
793 case 4:
794 $key = "price_for_m4";
795 break;
796 default:
797 throw new PaymentException("Wrong member type", 500);
798 break;
799 }
800
801 $configs = $this->dbConnection->Read('configs', ['min_qty', 'max_qty', 'unpaid_qty'], 'id', 1);
802 if ($qty < $configs['min_qty']) {
803 throw new PaymentException("Too few items!");
804 }
805 if ($qty > $configs['max_qty']) {
806 throw new PaymentException("Too many items!");
807 }
808
809 $paid_qty = ($qty - $configs['unpaid_qty']);
810 $value = 0;
811 $value+= $price[1][$key];
812 if ($paid_qty > 0) {
813 $value+= $price[0][$key] * $paid_qty;
814 }
815
816 $req = [
817 "title" => $title,
818 "user_id" => $user_id,
819 "payment_type" => 1, // This is the payment_type for payments based on PRICE
820 "value" => $value,
821 "status" => ($value > 0.01 ? 0 : 5)
822 ];
823 $id = $this->dbConnection->Create('payments', $req);
824 if ($paid) {
825 $this->dbConnection->Update('payments', ["status" => 5, "value" => 0], "id", $id);
826 }
827 return $id;
828 }
829
830
831 /**
832 * @param $data
833 * @param $user_id
834 * @return int
835 * @throws DbException
836 * @throws ValidationException
837 */
838 public function createPaymentFromAvailablePayment($data, $user_id, $paid): int
839 {
840 $new_member_type = null;
841 // Getting input data
842 $available_payment_id = $data["available_payment_id"];
843 $changed_member_type = $data["changed_member_type"];
844 if ($changed_member_type) {
845 $new_member_type = $data["new_member_type"];
846 }
847 $optional_payment = $data["optional_payment"];
848
849 // Getting member_type
850 $um = new UserModel();
851 $user = $um->getUser($user_id);
852 $member_type = $user["member_type"];
853
854
855
856 $requires_proof = [1, 3, 4];
857 if(in_array($user["member_type"], $requires_proof )){
858 throw new ValidationException("Proof file needed", 405);
859
860 if($requires_proof){
861 $proof_file = $data["proof_file"];
862 }
863 }
864
865 return $this->_createPaymentFromAvailablePayment(
866 $available_payment_id,
867 $user,
868 $member_type,
869 $paid,
870 $optional_payment,
871 $changed_member_type,
872 $new_member_type,
873 $proof_file
874 );
875 }
876
877
878 /**
879 * Creates a payment from available payments
880 *
881 * @param array $available_payment_id
882 * @param int $user_id
883 * @param int $member_type
884 * @param bool $optional_payment
885 * @param bool $changed_member_type
886 * @param int|null $new_member_type
887 * @return int
888 * @throws DbException
889 * @throws PaymentException
890 */
891 public function _createPaymentFromAvailablePayment(
892 array $available_payment_id,
893 array $user,
894 int $member_type,
895 bool $paid,
896 bool $optional_payment = false,
897 bool $changed_member_type = false,
898 ?int $new_member_type = null,
899 ?string $proof_file = null
900 ): int {
901 $value = 0;
902 $available_payments = [];
903
904 // First, we validate if the user can pay all the available payments
905 foreach ($available_payment_id as $ap_id) {
906 if (!$this->anuityPending($ap_id, $user["id"])) {
907 throw new PaymentException("There's already a payment for this annuity!", 406);
908 }
909
910 if($member_type==1
911 && $this->paymentEndDate($ap_id)>$this->aspirantLimit($user)
912 && (!$changed_member_type || $new_member_type==1)){
913 throw new PaymentException("You cannot stay as aspirant anymore!", 403);
914 }
915 // Then we get the payments and the value
916 $ap = $this->getAvailablePaymentPrice($ap_id, $member_type, $new_member_type);
917 $available_payments[] = $ap;
918 if ($this->canChange($ap['end_date']) && $optional_payment) {
919 $value += $ap["opt_price"];
920 } else {
921 $value += $ap["price"];
922 }
923 }
924
925 if ($changed_member_type) {
926 if (!$this->isValidMemberTypeChange($member_type, $new_member_type)) {
927 throw new PaymentException("Invalid member type change", 406);
928 }
929 }
930
931 $req = [
932 "title" => "Pagamento de anuidade",
933 "user_id" => $user["id"],
934 "payment_type" => 2,
935 "value" => $value,
936 "optional_payment" => $optional_payment,
937 "custom_member_type" => $changed_member_type,
938 "new_member_type" => $new_member_type,
939 "proof_file" => $proof_file,
940 "status" => ($value > 0.01 ? 0 : 5)
941 ];
942
943 // Creating payment
944 $payment_id = $this->dbConnection->Create('payments', $req);
945
946 // Creating mentions to it on relation table
947 foreach ($available_payments as $ap) {
948 $req = [
949 "available_payment_id" => $ap["id"],
950 "payment_id" => $payment_id,
951 ];
952 $this->dbConnection->Create('selected_available_payments', $req);
953 }
954
955 if ($paid) {
956 $this->dbConnection->Update('payments', ["status" => 5, "value" => 0], "id", $payment_id);
957 $this->updateUserDefaulting($user["id"]);
958 }
959
960 return $payment_id;
961 }
962
963 /**
964 * Endpoint for creating a payment, routes to createFromAvailablePayment/FromPrice
965 *
966 * @param array $data
967 * @param $user_id
968 * @return int
969 * @throws DbException
970 * @throws PaymentException
971 * @throws ValidationException
972 */
973 public function createPayment(array $data, $user_id, $token="")
974 {
975 $payment_type = $data["payment_type"];
976 $paid = false;
977
978 if (!empty($data["paid"])) {
979 $am = new AuthModel();
980 $auth = $am->getAuth($token);
981 if (!($auth["admin"] || $auth["payment"])) {
982 throw new PaymentException("Unauthorized", 401);
983 }
984 $paid = true;
985 }
986
987
988 if ($payment_type == 0) {
989 return $this->createPaymentFromPrice($data, $user_id, $paid);
990 } elseif ($payment_type == 1) {
991 return $this->createPaymentFromAvailablePayment($data, $user_id, $paid);
992 } else {
993 throw new PaymentException("Invalid payment type", 406);
994 }
995 }
996
997
998 /**
999 * Checks if a user can pay an available payment
1000 *
1001 * @param int $available_payment_id
1002 * @param int $user_id
1003 * @return bool
1004 * @throws DbException
1005 */
1006 public function anuityPending(int $available_payment_id, int $user_id): bool
1007 {
1008 $sql = "SELECT * FROM selected_available_payments sp
1009 JOIN payments p on (sp.payment_id = p.id)
1010 WHERE p.user_id = ? AND p.status != 6 AND sp.available_payment_id = ?";
1011 $res = $this->dbConnection->CustomQuery($sql, [$user_id, $available_payment_id]);
1012 return !count($res);
1013 }
1014
1015
1016 /**
1017 * Get payment's limit date
1018 *
1019 * @param int $available_payment_id
1020 * @return DateTime
1021 */
1022 private function paymentEndDate($ap_id): DateTime
1023 {
1024 $res = $this->dbConnection->Read("available_payments", ["end_date"], 'id', $ap_id);
1025 return DateTime::createFromFormat("Y-m-d H:i:s", $res["end_date"]);
1026 }
1027
1028 /**
1029 * Return the price, opt_price and end_date of a payment
1030 * @param int $available_payment_id
1031 * @param int $old_member_type
1032 * @param int $new_member_type
1033 * @return array
1034 * @throws DbException
1035 */
1036 protected function getAvailablePaymentPrice(int $available_payment_id, int $old_member_type, int $new_member_type=null)
1037 {
1038 if (is_null($new_member_type)) {
1039 $new_member_type = $old_member_type;
1040 }
1041 $columns = [
1042 "m_1_price", "m_2_price", "m_3_price", "m_4_price",
1043 "m_1_opt_price", "m_2_opt_price", "m_3_opt_price", "m_4_opt_price",
1044 "end_date"
1045 ];
1046 $res = $this->dbConnection->Read("available_payments", $columns, 'id', $available_payment_id);
1047
1048 $end_date = DateTime::createFromFormat("Y-m-d H:i:s", $res["end_date"]);
1049 $member_type = ($this->canChange($end_date) ? $new_member_type : $old_member_type);
1050 $key = "m_". $member_type . "_price";
1051 $price = $res["m_" . $member_type . "_price"];
1052 $opt_price = null;
1053 if ($member_type) {
1054 $opt_price = $res["m_" . $member_type . "_opt_price"];
1055 }
1056
1057 return [
1058 "id" => $available_payment_id,
1059 "price" => $price,
1060 "opt_price" => $opt_price,
1061 "end_date" => $end_date
1062 ];
1063 }
1064
1065 /**
1066 * Get available member_types for member_type changes
1067 *
1068 * @param $member_type
1069 * @return array
1070 */
1071 public function getAvailableMemberType($member_type): array
1072 {
1073 switch ($member_type) {
1074 case 0:
1075 return [0, 1, 2, 3, 4];
1076 case 1:
1077 return [1, 2];
1078 case 2:
1079 return [2];
1080 case 3:
1081 return [3];
1082 case 4:
1083 return [4];
1084 default:
1085 return [];
1086 }
1087 }
1088
1089 /**
1090 * Verifies if a member_type change is valid
1091 *
1092 * Uses getAvailableMemberType
1093 *
1094 * @param $old_member_type
1095 * @param $new_member_type
1096 * @return bool
1097 */
1098 protected function isValidMemberTypeChange($old_member_type, $new_member_type): bool
1099 {
1100 $available = $this->getAvailableMemberType($old_member_type);
1101 return in_array($new_member_type, $available);
1102 }
1103
1104 /**
1105 * Receives an available payment and returns a minified version
1106 * @param array $available_payment The available payment to minify
1107 * @param int $current_member_type The member type from the user
1108 * @param bool $can_change if the user can change the member type in this available payment
1109 * @return array
1110 */
1111 protected function beautifyAvailablePayment(array $available_payment, int $current_member_type, bool $can_change): array
1112 {
1113 if ($can_change) {
1114 $keys_for_member_type = [
1115 0 => ["m_1_price", "m_2_price", "m_2_opt_price", "m_3_price", "m_3_opt_price"],
1116 1 => ["m_1_price", "m_2_price", "m_2_opt_price"],
1117 2 => ["m_2_price", "m_2_opt_price"],
1118 3 => ["m_3_price", "m_3_opt_price"],
1119 4 => ["m_4_price", "m_4_opt_price"]
1120 ];
1121 } else {
1122 $keys_for_member_type = [
1123 0 => ["m_1_price", "m_2_price", "m_2_opt_price", "m_3_price", "m_3_opt_price", "m_4_price", "m_4_opt_price"],
1124 1 => ["m_1_price"],
1125 2 => ["m_2_price"],
1126 3 => ["m_3_price"],
1127 3 => ["m_4_price"]
1128 ];
1129 }
1130 $keys = array_merge($keys_for_member_type[$current_member_type], ['id', 'title', 'start_date', 'end_date']);
1131 return $this->array_select_keys($available_payment, $keys);
1132 }
1133
1134 protected function array_select_keys(array $a, array $b): array
1135 {
1136 foreach ($b as $c) {
1137 $d[$c] = $a[$c];
1138 }
1139 return $d;
1140 }
1141
1142
1143 /**
1144 * Checks if a date is in this year or in the future
1145 * @param DateTime $payment_end_date
1146 * @return bool
1147 */
1148 protected function canChange(DateTime $payment_end_date): bool
1149 {
1150 $current_date = new DateTime();
1151 return ((int)$payment_end_date->format('Y') >= (int)$current_date->format('Y'));
1152 }
1153
1154 /**
1155 * Endpoint for paying
1156 */
1157 public function pay($payment_id, $data, $token)
1158 {
1159 $am = new AuthModel();
1160 $user_id = $am->getUserFromToken($token);
1161 $payment_id = v::validateId($payment_id);
1162
1163 if (!$this->isPaymentFromUser($payment_id, $user_id)) {
1164 throw new PaymentException("This payment ain't from this user", 406);
1165 }
1166
1167 // General
1168 $payment = $this->dbConnection->Read("payments", ['id', 'title', 'value'], "id", $payment_id);
1169 $sender = v::validateSender($data['sender']);
1170
1171 // Processing items
1172 $items = $this->getPaymentItems($payment_id);
1173 $pm = new PagseguroModel();
1174
1175 if ($data['payment_method'] == 'boleto') {
1176 $total_value_string = (string) $payment['value'];
1177 $purchase = $pm->purchaseWithBoleto($sender, $items, $total_value_string);
1178 $this->updatePaymentFromId($payment_id, $purchase["code"], 1, "boleto");
1179 return $purchase["url"];
1180 } elseif ($data['payment_method'] == 'cc') {
1181 $holder = v::validateHolder($data['holder']);
1182 $cc_token = v::validateCCtoken($data['cc_token']);
1183 $address = v::validateAddress($data['address']);
1184 $total_value_string = (string) $payment['value'];
1185 $brand = $data['brand'];
1186 $installment = (empty($data['installment']) ? 1 : $data['installment']);
1187
1188 $purchase = $pm->purchaseWithCreditCard(
1189 $sender,
1190 $items,
1191 $cc_token,
1192 $holder,
1193 $address,
1194 $total_value_string,
1195 $brand,
1196 $installment
1197 );
1198 $this->updatePaymentFromId($payment_id, $purchase["code"], 1, "cc");
1199
1200 return $purchase;
1201 } elseif ($data['payment_method'] == 'free') {
1202 if($payment['value'] >= 0.001)
1203 throw new PaymentException("Hey, this isn't free :P", 406);
1204 } else {
1205 throw new PaymentException("Invalid payment method", 406);
1206 }
1207 }
1208
1209 /**
1210 * Returns true if payment is from user, false if isn't
1211 * @param int $payment_id
1212 * @param int $user_id
1213 * @throws DbException
1214 * @return bool
1215 */
1216 public function isPaymentFromUser($payment_id, $user_id): bool
1217 {
1218 $data = $this->dbConnection->Read("payments", ["user_id"], "id", $payment_id);
1219 return ($data["user_id"] == $user_id);
1220 }
1221
1222 /**
1223 * Returns the limit date that a member can stay as aspirant
1224 * @param object $user
1225 * @throws DbException
1226 * @return object
1227 */
1228 private function aspirantLimit(array $user)
1229 {
1230 if($user["member_type"] == 1){
1231 return DateTime::createFromFormat("Y-m-d H:i:s", $user['filiated_at'])
1232 ->add(new DateInterval("P3Y"));
1233 }
1234 else{
1235 return false;
1236 }
1237 }
1238
1239 /**
1240 * Returns the payment items
1241 * @param int $payment_id
1242 * @throws DbException
1243 * @return array
1244 */
1245 private function getPaymentItems(int $payment_id): array
1246 {
1247 $payment = $this->_readPayment($payment_id);
1248 $items = [];
1249 if ($payment["payment_type"] = 1) { // payment from price
1250 $items[] = [
1251 "name" => $payment["title"],
1252 "qty" => 1,
1253 "value" => $payment["value"]
1254 ];
1255 } elseif ($payment["payment_type"] = 2) {
1256 $available_payments = $this->getAvailablePaymentsFromPayment($payment_id);
1257 $items[] = [
1258 "name" => ("Anuidade(s) referentes a"
1259 . implode(", ", array_column($available_payments, "title"))),
1260 "qty" => 1,
1261 "value" => $payment["value"]
1262 ];
1263 }
1264 return $items;
1265 }
1266
1267 /**
1268 * Returns available payments selected in a payment
1269 * @param int $payment_id
1270 * @throws DbException
1271 * @return array
1272 */
1273 private function getAvailablePaymentsFromPayment(int $payment_id): array
1274 {
1275 return ($this->dbConnection
1276 ->CustomQuery("SELECT ap.title FROM selected_available_payments sp
1277 JOIN available_payments ap
1278 ON (ap.id = sp.available_payment_id)
1279 WHERE sp.payment_id = ?", [$payment_id]));
1280 }
1281
1282 /**
1283 * Updates a payment from its id
1284 * @param id
1285 * @param pagseguro_id
1286 * @param status
1287 * @param method
1288 * @throws DbException
1289 * @return bool
1290 */
1291 public function updatePaymentFromId($id, $pagseguro_id, $status, $method=null)
1292 {
1293 $req = [
1294 "pagseguro_id" => $pagseguro_id,
1295 "status" => $status
1296 ];
1297
1298 if (!is_null($method)) {
1299 $req["method"] = $method;
1300 }
1301
1302 $res = $this->dbConnection->Update("payments", $req, "id", $id);
1303 $p = $this->dbConnection->Read("payments", ["user_id"], "id", $id);
1304 $this->updateUserDefaulting($p["user_id"]);
1305 return $res;
1306 }
1307
1308 public function updatePaymentFromPagseguroId($pagseguro_id, $status)
1309 {
1310 $req = [
1311 "status" => $status
1312 ];
1313 $res = $this->dbConnection->Update(
1314 "payments",
1315 $req,
1316 "pagseguro_id",
1317 $pagseguro_id
1318 );
1319 $p = $this->dbConnection->Read("payments", ["user_id"], "pagseguro_id", $pagseguro_id);
1320 if(!empty($p)){
1321 $this->updateUserDefaulting($p["user_id"]);
1322 }
1323 return $res;
1324 }
1325
1326 /**
1327 * Gets a payment
1328 *
1329 * @param int $id payment id
1330 * @param string $locale defaults to "en"
1331 */
1332 public function getPayment(int $id, string $locale="en")
1333 {
1334 $res = $this->_getPayment($id, $locale);
1335 $fixed_res = [
1336 "status_code" => $res["status"],
1337 "status" => $this->getStatusText($res["status"], $locale),
1338 "method" => $this->getMethodText($res["method"], $locale),
1339 "created_at" => v::dateReverse($res["created_at"]),
1340 "updated_at" => v::dateReverse($res["updated_at"])
1341 ];
1342
1343 $fixed_res = array_merge($res, $fixed_res);
1344 return $fixed_res;
1345 }
1346
1347 public function _getPayment(int $id, $locale)
1348 {
1349 $columns = ["id", "title", "payment_type", "value", "status",
1350 "custom_member_type", "method", "created_at",
1351 "updated_at", "user_id", "pagseguro_id", "optional_payment", "proof_file"];
1352 $ap_columns = ["title"];
1353 $payment = $this->dbConnection->Read("payments", $columns, "id", $id);
1354
1355 // Getting selected available payments
1356 if ($payment["payment_type"] == 2) {
1357 $sp = $this->dbConnection->ReadAllWhere(
1358 "selected_available_payments",
1359 ["available_payment_id"],
1360 "payment_id",
1361 $id
1362 );
1363
1364 $sp = array_column($sp, "available_payment_id");
1365
1366 foreach ($sp as $ap_id) {
1367 $aps[] = $this->dbConnection->Read(
1368 "available_payments",
1369 $ap_columns,
1370 "id",
1371 $ap_id
1372 );
1373 }
1374 $payment["available_payments"] = $aps;
1375 }
1376
1377
1378 if($payment["method"] == "boleto"
1379 && $payment["status"] < 2){
1380 $pm = new PagseguroModel();
1381 $payment["url"] = $pm->getBoletoUrl($payment["pagseguro_id"]);
1382 }
1383
1384 return $payment;
1385 }
1386
1387 /**
1388 * Endpoint for cancelling a payment
1389 * @param $data
1390 * @param $payment_id
1391 */
1392 public function cancelPayment($data, $payment_id)
1393 {
1394 $payment_id = (int)$payment_id;
1395 return $this->_cancelPayment($payment_id);
1396 }
1397
1398 /**
1399 * Cancels a payment
1400 * @param int $payment_id
1401 * @throws DbException
1402 * @throws PaymentException
1403 * @return bool
1404 */
1405 public function _cancelPayment(int $payment_id): bool
1406 {
1407 $payment = $this->dbConnection->Read("payments", ["status"], "id", $payment_id);
1408 if ($payment["status"] == 5) {
1409 throw new PaymentException("You can't delete a concluded payment", 401);
1410 }
1411 return $this->dbConnection->Update("payments", ["status" => 6], "id", $payment_id);
1412 }
1413
1414 /**
1415 * Edit an available payment
1416 *
1417 * @param $data
1418 * @param $available_payment_id
1419 */
1420 public function editAvailablePayment($data, $available_payment_id)
1421 {
1422 $req = [
1423 "title" => v::validateTitle($data["title"]),
1424 "m_1_price" => v::validateMoney($data["m_1_price"]),
1425 "m_2_price" => v::validateMoney($data["m_2_price"]),
1426 "m_3_price" => v::validateMoney($data["m_3_price"]),
1427 "m_4_price" => v::validateMoney($data["m_4_price"]),
1428 "m_1_opt_price" => v::validateMoney($data["m_1_opt_price"]),
1429 "m_2_opt_price" => v::validateMoney($data["m_2_opt_price"]),
1430 "m_3_opt_price" => v::validateMoney($data["m_3_opt_price"]),
1431 "m_4_opt_price" => v::validateMoney($data["m_4_opt_price"]),
1432 "start_date" => v::validateDate($data["start_date"]),
1433 "end_date" => v::validateDate($data["end_date"]),
1434 ];
1435 return $this->dbConnection->Update("available_payments", $req, "id", $available_payment_id);
1436 }
1437
1438 public function inactivateDefaulting()
1439 {
1440 $where = "WHERE member_type != 0
1441 AND active = 1
1442 AND filiated_at<SUBDATE(CURRENT_DATE(), INTERVAL 3 YEAR)
1443 AND NOT EXISTS
1444 (SELECt *
1445 FROM payments
1446 WHERE user_id=users.id
1447 AND updated_at>SUBDATE(CURRENT_DATE(), INTERVAL 3 YEAR)
1448 AND status=5
1449 AND payment_type=2
1450 )
1451 ORDER BY id DESC";
1452
1453 $results = $this->dbConnection->CustomQuery("SELECT id FROM users $where");
1454
1455 $this->dbConnection->CustomQuery("UPDATE users SET active=0, forced_inactivation=CURRENT_DATE() $where");
1456 $mm = new MailingModel();
1457 foreach($results as $row){
1458 $mm->inactivation($row["id"]);
1459 }
1460 }
1461
1462 // TODO: substitute this for database stuff. sorry saccol, i'm in a hurry right now
1463 private function getStatusText($status, $locale="en")
1464 {
1465 $foo = [
1466 "pt-BR" => [
1467 "Aguardando pagamento",
1468 "Aguardando pagamento",
1469 "Em análise",
1470 "",
1471 "",
1472 "Concluído",
1473 "Cancelado"
1474 ],
1475 "en" => [
1476 "Waiting payment",
1477 "Waiting payment",
1478 "In analysis",
1479 "",
1480 "",
1481 "Concluded",
1482 "Canceled"
1483 ]
1484 ];
1485 return $foo[$locale][$status];
1486 }
1487
1488 private function getMethodText($method, $locale="en")
1489 {
1490 $foo = [
1491 "pt-BR" => "Abono",
1492 "en" => "Allowance"
1493 ];
1494
1495 $bar = [
1496 "pt-BR" => ["cc" => "Cartão de crédito",
1497 "boleto" => "Boleto bancário",
1498 "free" => "Gratuito"
1499 ],
1500 "en" => ["cc" => "Credit card",
1501 "boleto" => "Boleto bancário",
1502 "free" => "Free"
1503 ]
1504 ];
1505
1506 if (is_null($method)) {
1507 return $foo[$locale];
1508 }
1509
1510 return $bar[$locale][$method];
1511 }
1512}
1513