CprFastTrackController.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. <?php
  2. namespace Controllers;
  3. use Libs\ResponseLib;
  4. use Models\CprModel;
  5. use Models\CommodityModel;
  6. use Models\StatusModel;
  7. use Models\PaymentModel;
  8. use Psr\Http\Message\ServerRequestInterface;
  9. use Services\TokenCreateService;
  10. class CprFastTrackController
  11. {
  12. private \PDO $pdo;
  13. private CprModel $cprModel;
  14. private CommodityModel $commodityModel;
  15. private StatusModel $statusModel;
  16. private PaymentModel $paymentModel;
  17. private TokenCreateService $tokenCreateService;
  18. public function __construct()
  19. {
  20. if (!isset($GLOBALS['pdo']) || !$GLOBALS['pdo'] instanceof \PDO) {
  21. throw new \RuntimeException('Global PDO connection not initialized');
  22. }
  23. $this->pdo = $GLOBALS['pdo'];
  24. $this->cprModel = new CprModel();
  25. $this->commodityModel = new CommodityModel();
  26. $this->statusModel = new StatusModel();
  27. $this->paymentModel = new PaymentModel();
  28. $this->tokenCreateService = new TokenCreateService();
  29. }
  30. public function __invoke(ServerRequestInterface $request)
  31. {
  32. $body = json_decode((string)$request->getBody(), true) ?? [];
  33. $cprPayload = isset($body['cpr']) && is_array($body['cpr']) ? $body['cpr'] : $body;
  34. $userId = isset($body['user_id']) ? (int)$body['user_id'] : 0;
  35. $companyId = isset($body['company_id']) ? (int)$body['company_id'] : 0;
  36. if ($userId <= 0 || $companyId <= 0) {
  37. $pair = $this->findAnyActiveUser();
  38. $userId = $pair['user_id'];
  39. $companyId = $pair['company_id'];
  40. }
  41. try {
  42. $statusId = $this->statusModel->getIdByStatus('pending');
  43. if ($statusId === null) {
  44. throw new \RuntimeException('Pending status not found');
  45. }
  46. $paymentExternal = 'FAST_' . bin2hex(random_bytes(6));
  47. $paymentId = $this->paymentModel->create(
  48. $paymentExternal,
  49. PaymentModel::STATUS_COMPLETED,
  50. $userId,
  51. time(),
  52. '',
  53. 'fast-track'
  54. );
  55. $cpr = $this->cprModel->create($cprPayload, $statusId, $paymentId, $userId, $companyId);
  56. $inputs = $this->prepareTokenInputs($cpr, $companyId, $userId);
  57. $token = $this->tokenCreateService->createToken(
  58. $inputs['token_commodities_amount'],
  59. $inputs['token_commodities_value'],
  60. $inputs['token_uf'],
  61. $inputs['token_city'],
  62. (string)$cpr['cpr_id'],
  63. '',
  64. $inputs['wallet_id'],
  65. $inputs['chain_id'],
  66. $inputs['commodities_id'],
  67. (int)$cpr['cpr_id'],
  68. $userId
  69. );
  70. $this->cprModel->updateTokenId((int)$cpr['cpr_id'], (int)$token['token_id']);
  71. return ResponseLib::sendOk([
  72. 'message' => 'CPR registrada e token mintado (fast-track)',
  73. 'cpr_id' => (int)$cpr['cpr_id'],
  74. 'payment_id' => $paymentId,
  75. 'token_id' => (int)$token['token_id'],
  76. 'token_external_id' => $token['token_external_id'],
  77. 'tx_hash' => $token['tx_hash'],
  78. ], 'S_CPR_FAST');
  79. } catch (\Throwable $e) {
  80. return ResponseLib::sendFail('Falha no fast-track: ' . $e->getMessage(), [], 'E_FAST_TRACK')->withStatus(500);
  81. }
  82. }
  83. private function findAnyActiveUser(): array
  84. {
  85. $stmt = $this->pdo->query("SELECT user_id, company_id FROM \"user\" WHERE user_flag = 'a' ORDER BY user_id LIMIT 1");
  86. $row = $stmt->fetch(\PDO::FETCH_ASSOC);
  87. if (!$row) {
  88. throw new \RuntimeException('Nenhum usuário ativo encontrado');
  89. }
  90. return ['user_id' => (int)$row['user_id'], 'company_id' => (int)$row['company_id']];
  91. }
  92. private function prepareTokenInputs(array $cpr, int $companyId, int $userId): array
  93. {
  94. $wallet = $this->findWalletByCompanyId($companyId);
  95. $commoditiesName = $this->requireStringField($cpr, ['cpr_product_name'], 'cpr_product_name');
  96. $commoditiesId = $this->resolveCommodityId($commoditiesName);
  97. $amount = $this->requireNumericField($cpr, ['cpr_product_quantity', 'cpr_issue_quantity'], 'quantidade do produto');
  98. $value = $this->requireNumericField($cpr, ['cpr_issue_value', 'cpr_issue_financial_value'], 'valor do produto');
  99. $uf = $this->requireStringField($cpr, ['cpr_deliveryPlace_state_acronym', 'cpr_issuers_state_acronym'], 'UF');
  100. $city = $this->requireStringField($cpr, ['cpr_deliveryPlace_city_name', 'cpr_issuers_city_name'], 'cidade');
  101. return [
  102. 'token_commodities_amount' => $amount,
  103. 'token_commodities_value' => $value,
  104. 'token_uf' => $uf,
  105. 'token_city' => $city,
  106. 'wallet_id' => $wallet['wallet_id'],
  107. 'chain_id' => $wallet['chain_id'],
  108. 'commodities_id' => $commoditiesId,
  109. 'user_id' => $userId,
  110. ];
  111. }
  112. private function findWalletByCompanyId(int $companyId): array
  113. {
  114. $stmt = $this->pdo->prepare('SELECT wallet_id, chain_id FROM "wallet" WHERE company_id = :company_id ORDER BY wallet_id ASC LIMIT 1');
  115. $stmt->execute(['company_id' => $companyId]);
  116. $wallet = $stmt->fetch(\PDO::FETCH_ASSOC);
  117. if (!$wallet) {
  118. throw new \RuntimeException('Nenhuma carteira encontrada para a empresa informada');
  119. }
  120. return ['wallet_id' => (int)$wallet['wallet_id'], 'chain_id' => (int)$wallet['chain_id']];
  121. }
  122. private function resolveCommodityId(string $name): int
  123. {
  124. $id = $this->commodityModel->getIdByName($name);
  125. if ($id === null) {
  126. throw new \RuntimeException('Commodity não encontrada para o produto: ' . $name);
  127. }
  128. return $id;
  129. }
  130. private function requireStringField(array $cpr, array $candidates, string $label): string
  131. {
  132. foreach ($candidates as $field) {
  133. if (array_key_exists($field, $cpr)) {
  134. $v = $this->normalizeStringValue($cpr[$field]);
  135. if ($v !== '') {
  136. return $v;
  137. }
  138. }
  139. }
  140. throw new \InvalidArgumentException("Campo {$label} ausente ou inválido na CPR.");
  141. }
  142. private function requireNumericField(array $cpr, array $candidates, string $label): int
  143. {
  144. foreach ($candidates as $field) {
  145. if (array_key_exists($field, $cpr)) {
  146. $v = $this->normalizeNumericValue($cpr[$field]);
  147. if ($v !== null) {
  148. return $v;
  149. }
  150. }
  151. }
  152. throw new \InvalidArgumentException("Campo {$label} ausente ou inválido na CPR.");
  153. }
  154. private function normalizeStringValue($value): string
  155. {
  156. if (is_array($value)) {
  157. $value = reset($value);
  158. }
  159. if (!is_scalar($value)) {
  160. return '';
  161. }
  162. $s = trim((string)$value);
  163. if ($s === '') {
  164. return '';
  165. }
  166. $parts = preg_split('/\s*;\s*/', $s) ?: [];
  167. $first = $parts[0] ?? $s;
  168. return trim((string)$first);
  169. }
  170. private function normalizeNumericValue($value): ?int
  171. {
  172. if (is_array($value)) {
  173. $value = reset($value);
  174. }
  175. if (is_string($value)) {
  176. $value = str_replace([' ', ','], ['', '.'], $value);
  177. }
  178. if (is_numeric($value)) {
  179. return (int)round((float)$value);
  180. }
  181. return null;
  182. }
  183. }