OrderResponseData.php 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. <?php
  2. namespace App\Data\Pagarme\Order;
  3. use App\Data\Pagarme\Customer\CustomerResponseData;
  4. use App\Data\Pagarme\Order\Parts\Response\ChargeData;
  5. use App\Data\Pagarme\Order\Parts\Response\CheckoutData;
  6. use App\Data\Pagarme\Order\Parts\Response\ItemData;
  7. use App\Data\Pagarme\Order\Parts\Response\TransactionData;
  8. use App\Data\Pagarme\PagarmeResponseData;
  9. use App\Enums\PaymentStatusEnum;
  10. /**
  11. * @param ItemData[] $items
  12. * @param ChargeData[] $charges
  13. * @param CheckoutData[] $checkouts
  14. */
  15. final readonly class OrderResponseData extends PagarmeResponseData
  16. {
  17. public function __construct(
  18. public ?string $id,
  19. public ?string $code,
  20. public ?int $amount,
  21. public ?string $currency,
  22. public ?bool $closed,
  23. public ?string $status,
  24. public array $items,
  25. public ?CustomerResponseData $customer,
  26. public array $charges,
  27. public array $checkouts,
  28. public array $metadata,
  29. public ?string $createdAt = null,
  30. public ?string $updatedAt = null,
  31. public ?string $closedAt = null,
  32. ) {}
  33. public function authorizedAt(): ?string
  34. {
  35. $transaction = $this->lastTransaction();
  36. if (! $transaction) {
  37. return null;
  38. }
  39. if (in_array($transaction->status, ['authorized_pending_capture', 'captured', 'partial_capture'], true)) {
  40. return $transaction->createdAt;
  41. }
  42. return null;
  43. }
  44. public function failureCode(): ?string
  45. {
  46. $transaction = $this->lastTransaction();
  47. if (! $transaction) {
  48. return null;
  49. }
  50. $code = $this->filledValue($transaction->gatewayResponse['code'] ?? null);
  51. if ($code === null || $this->isMisleadingFailureCode($code)) {
  52. return $this->filledValue($transaction->status);
  53. }
  54. return $code;
  55. }
  56. public function failureMessage(): ?string
  57. {
  58. $transaction = $this->lastTransaction();
  59. if (! $transaction) {
  60. return null;
  61. }
  62. $acquirerMessage = $this->filledValue($transaction->acquirerMessage);
  63. if ($acquirerMessage && ! $this->isMisleadingAcquirerMessage($acquirerMessage)) {
  64. return $acquirerMessage;
  65. }
  66. $gatewayErrors = $transaction->gatewayResponse['errors'] ?? [];
  67. if (! is_array($gatewayErrors) || empty($gatewayErrors)) {
  68. return $this->failureMessageFromStatus($transaction->status);
  69. }
  70. $message = collect($gatewayErrors)
  71. ->pluck('message')
  72. ->filter()
  73. ->implode('; ') ?: null;
  74. if ($message && str_contains($message, 'Sem ambiente configurado')) {
  75. return 'Pix não esta habilitado ou configurado neste ambiente do Pagar.me.';
  76. }
  77. return $message;
  78. }
  79. public function firstCharge(): ?ChargeData
  80. {
  81. return $this->charges[0] ?? null;
  82. }
  83. public function gatewayEntityLabel(): string
  84. {
  85. $charge = $this->firstCharge();
  86. return $charge?->id ? 'charge' : 'order';
  87. }
  88. public function gatewayEntityReference(): ?string
  89. {
  90. $charge = $this->firstCharge();
  91. return $charge?->id ?? $this->id;
  92. }
  93. public function gatewayOperationLabel(): string
  94. {
  95. $charge = $this->firstCharge();
  96. $transaction = $this->lastTransaction();
  97. return $transaction?->id ? 'transaction' : ($charge?->id ? 'charge' : 'order');
  98. }
  99. public function gatewayOperationReference(): ?string
  100. {
  101. $charge = $this->firstCharge();
  102. $transaction = $this->lastTransaction();
  103. return $transaction?->id ?? $charge?->id ?? $this->id;
  104. }
  105. public function lastTransaction(): ?TransactionData
  106. {
  107. return $this->firstCharge()?->transaction();
  108. }
  109. public function paymentStatus(): PaymentStatusEnum
  110. {
  111. $charge = $this->firstCharge();
  112. $transaction = $this->lastTransaction();
  113. $status = strtolower((string) ($transaction?->status ?: $charge?->status));
  114. return match ($status) {
  115. 'captured', 'paid', 'overpaid' => PaymentStatusEnum::PAID,
  116. 'authorized_pending_capture', 'waiting_capture' => PaymentStatusEnum::AUTHORIZED,
  117. 'pending', 'waiting_payment' => PaymentStatusEnum::PENDING,
  118. 'processing' => PaymentStatusEnum::PROCESSING,
  119. 'not_authorized', 'with_error', 'failed',
  120. 'underpaid', 'chargedback' => PaymentStatusEnum::FAILED,
  121. 'voided', 'partial_void', 'canceled',
  122. 'cancelled', 'refunded', 'partial_refunded',
  123. 'partial_canceled' => PaymentStatusEnum::CANCELLED,
  124. default => PaymentStatusEnum::PENDING,
  125. };
  126. }
  127. public function paidAt(): ?string
  128. {
  129. return $this->firstCharge()?->paidAt;
  130. }
  131. public function requireId(): string
  132. {
  133. if (! $this->id) {
  134. throw new \RuntimeException('Pagar.me order creation returned an empty id.');
  135. }
  136. return $this->id;
  137. }
  138. //
  139. public static function fromArray(array $payload): static
  140. {
  141. $customer = static::arrArray($payload, 'customer');
  142. return new self(
  143. id: static::arrString($payload, 'id'),
  144. code: static::arrString($payload, 'code'),
  145. amount: static::arrInt($payload, 'amount'),
  146. currency: static::arrString($payload, 'currency'),
  147. closed: static::arrBool($payload, 'closed'),
  148. status: static::arrString($payload, 'status'),
  149. items: static::arrMap($payload, 'items',
  150. static fn (array $item) => ItemData::fromArray($item),
  151. ),
  152. customer: ! empty($customer)
  153. ? CustomerResponseData::fromArray($customer)
  154. : null,
  155. charges: static::arrMap($payload, 'charges',
  156. static fn (array $charge) => ChargeData::fromArray($charge),
  157. ),
  158. checkouts: static::arrMap($payload, 'checkouts',
  159. static fn (array $checkout) => CheckoutData::fromArray($checkout),
  160. ),
  161. metadata: static::arrArray($payload, 'metadata'),
  162. createdAt: static::arrString($payload, 'created_at'),
  163. updatedAt: static::arrString($payload, 'updated_at'),
  164. closedAt: static::arrString($payload, 'closed_at'),
  165. );
  166. }
  167. public function toArray(): array
  168. {
  169. return [
  170. 'id' => $this->id,
  171. 'code' => $this->code,
  172. 'amount' => $this->amount,
  173. 'currency' => $this->currency,
  174. 'closed' => $this->closed,
  175. 'items' => array_map(
  176. static fn (ItemData $item) => $item->toArray(),
  177. $this->items,
  178. ),
  179. 'customer' => $this->customer?->toArray(),
  180. 'status' => $this->status,
  181. 'created_at' => $this->createdAt,
  182. 'updated_at' => $this->updatedAt,
  183. 'closed_at' => $this->closedAt,
  184. 'charges' => array_map(
  185. static fn (ChargeData $charge) => $charge->toArray(),
  186. $this->charges,
  187. ),
  188. 'checkouts' => array_map(
  189. static fn (CheckoutData $checkout) => $checkout->toArray(),
  190. $this->checkouts,
  191. ),
  192. 'metadata' => $this->metadata,
  193. ];
  194. }
  195. //
  196. private function isMisleadingFailureCode(string $code): bool
  197. {
  198. // filtra para nao incluir codigos http que nao sao de erro
  199. if (preg_match('/^[1-5]\d{2}$/', $code)) {
  200. return true;
  201. }
  202. $lower = mb_strtolower($code);
  203. $successCodes = ['00', '0', 'approved', 'success'];
  204. return in_array($lower, $successCodes, true);
  205. }
  206. private function isMisleadingAcquirerMessage(string $message): bool
  207. {
  208. $lower = mb_strtolower($message);
  209. $successPatterns = [
  210. 'aprovada',
  211. 'aprovado',
  212. 'autorizada',
  213. 'autorizado',
  214. 'authorized',
  215. 'sucesso',
  216. ];
  217. foreach ($successPatterns as $pattern) {
  218. if (str_contains($lower, $pattern)) {
  219. return true;
  220. }
  221. }
  222. return false;
  223. }
  224. //
  225. private function failureMessageFromStatus(?string $status): ?string
  226. {
  227. return match (strtolower((string) $status)) {
  228. 'not_authorized' => 'Transação não autorizada pela operadora do cartão.',
  229. 'with_error' => 'Erro ao processar a transação.',
  230. 'failed' => 'Transação falhou.',
  231. 'underpaid' => 'Valor pago inferior ao esperado.',
  232. 'chargedback' => 'Transação sofreu chargeback.',
  233. default => null,
  234. };
  235. }
  236. private function filledValue(mixed $value): ?string
  237. {
  238. if ($value === null || $value === '' || $value === []) {
  239. return null;
  240. }
  241. return (string) $value;
  242. }
  243. }