PagarmeOrderRequestData.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. namespace App\Data\Pagarme\Request\PagarmeOrderRequestData;
  3. use App\Data\Pagarme\PagarmeData;
  4. use App\Models\PaymentSplit;
  5. use Illuminate\Support\Collection;
  6. /**
  7. * @param array<int, array{code?: string, amount?: int, quantity?: int, description?: string, ...}> $items Lista de itens do pedido
  8. * @param array<int, array{payment_method: string, ...}> $payments Lista de formas de pagamento
  9. * @param array<string, mixed> $metadata Metadados do pedido (ex: payment_id, schedule_id, client_id, provider_id)
  10. * @param array<string, mixed>|null $customer Dados do cliente (name, email, document, type, etc.)
  11. */
  12. final readonly class PagarmeOrderRequestData extends PagarmeData
  13. {
  14. public function __construct(
  15. public string $code,
  16. public array $items,
  17. public array $payments,
  18. public array $metadata,
  19. public ?array $customer = null,
  20. public ?string $customerId = null,
  21. public bool $closed = true,
  22. public ?string $channel = null,
  23. ) {
  24. self::requireFilled($this->code, 'code');
  25. if (empty($this->items)) {
  26. throw new \InvalidArgumentException('items nao pode estar vazio.');
  27. }
  28. if (empty($this->payments)) {
  29. throw new \InvalidArgumentException('payments nao pode estar vazio.');
  30. }
  31. foreach ($this->payments as $index => $payment) {
  32. if (! is_array($payment) || empty($payment['payment_method'])) {
  33. throw new \InvalidArgumentException("payments.{$index}.payment_method e obrigatorio.");
  34. }
  35. self::requireIn($payment['payment_method'], ['credit_card', 'pix'], "payments.{$index}.payment_method");
  36. }
  37. if (! $this->customerId && empty($this->customer)) {
  38. throw new \InvalidArgumentException('customer ou customer_id e obrigatorio.');
  39. }
  40. }
  41. public static function fromOrderPayload(
  42. string $code,
  43. array $items,
  44. array $customer,
  45. array $paymentMethod,
  46. array $metadata,
  47. mixed $customerId = null,
  48. bool $closed = true,
  49. ?string $channel = null,
  50. ): self {
  51. if (empty($items)) {
  52. throw new \InvalidArgumentException('items nao pode estar vazio.');
  53. }
  54. if (empty($paymentMethod['payment_method'])) {
  55. throw new \InvalidArgumentException('payment_method e obrigatorio.');
  56. }
  57. if (! in_array($paymentMethod['payment_method'], ['credit_card', 'pix'], true)) {
  58. throw new \InvalidArgumentException('payment_method deve ser credit_card ou pix.');
  59. }
  60. $customerIdPayload = self::filled($customerId) ? (string) $customerId : null;
  61. $customerPayload = self::filterFilledRecursiveStatic($customer);
  62. if (! $customerIdPayload && empty($customerPayload)) {
  63. throw new \InvalidArgumentException('customer ou customer_id e obrigatorio.');
  64. }
  65. return new self(
  66. code: $code,
  67. items: self::validateItems($items),
  68. payments: [self::filterFilledRecursiveStatic($paymentMethod)],
  69. metadata: $metadata,
  70. customer: ! empty($customerPayload) ? $customerPayload : null,
  71. customerId: $customerIdPayload,
  72. closed: $closed,
  73. channel: $channel,
  74. );
  75. }
  76. //
  77. public static function amountInCents(float $amount): int
  78. {
  79. return (int) round($amount * 100);
  80. }
  81. public static function creditCardPaymentMethod(array $creditCard, ?array $split = null): array
  82. {
  83. return self::paymentMethodWithOptionalSplit([
  84. 'payment_method' => 'credit_card',
  85. 'credit_card' => self::buildCreditCardPayload($creditCard),
  86. ], $split);
  87. }
  88. public static function pixPaymentMethod(array $pix, ?array $split = null): array
  89. {
  90. return self::paymentMethodWithOptionalSplit([
  91. 'payment_method' => 'pix',
  92. 'pix' => self::buildPixPayload($pix),
  93. ], $split);
  94. }
  95. public static function splitFromTransfers(Collection $transfers): array
  96. {
  97. return $transfers
  98. ->filter(fn (PaymentSplit $split) => ! empty($split->gateway_transfer_target_reference))
  99. ->map(function (PaymentSplit $split) {
  100. return [
  101. 'amount' => self::amountInCents((float) $split->gross_amount),
  102. 'recipient_id' => $split->gateway_transfer_target_reference,
  103. 'type' => 'flat',
  104. 'options' => [
  105. 'charge_processing_fee' => false,
  106. 'charge_remainder_fee' => false,
  107. 'liable' => false,
  108. ],
  109. ];
  110. })
  111. ->values()
  112. ->all();
  113. }
  114. //
  115. public function toArray(): array
  116. {
  117. return $this->filterFilledRecursive([
  118. 'code' => $this->code,
  119. 'items' => $this->items,
  120. 'payments' => $this->payments,
  121. 'closed' => $this->closed,
  122. 'metadata' => $this->metadata,
  123. 'customer_id' => $this->customerId,
  124. 'customer' => $this->customer,
  125. 'channel' => $this->channel,
  126. ]);
  127. }
  128. //
  129. private static function buildCreditCardPayload(array $creditCard): array
  130. {
  131. $payload = [];
  132. foreach ([
  133. 'installments',
  134. 'statement_descriptor',
  135. 'operation_type',
  136. 'recurrence_cycle',
  137. 'metadata',
  138. 'extended_limit_enabled',
  139. 'extended_limit_code',
  140. 'merchant_category_code',
  141. 'authentication',
  142. 'auto_recovery',
  143. 'payload',
  144. 'payment_type',
  145. 'funding_source',
  146. 'initiated_type',
  147. 'recurrence_model',
  148. 'channel',
  149. 'payment_origin',
  150. ] as $field) {
  151. if (array_key_exists($field, $creditCard) && self::filled($creditCard[$field])) {
  152. $payload[$field] = $creditCard[$field];
  153. }
  154. }
  155. $allowedCardOptions = ['card', 'card_id', 'card_token', 'network_token'];
  156. $provided = array_values(array_filter(
  157. $allowedCardOptions,
  158. static fn (string $field) => ! empty($creditCard[$field])
  159. ));
  160. if (count($provided) !== 1) {
  161. throw new \InvalidArgumentException('Informe exatamente uma opcao entre card, card_id, card_token ou network_token.');
  162. }
  163. $selected = $provided[0];
  164. $payload[$selected] = $creditCard[$selected];
  165. return $payload;
  166. }
  167. private static function buildPixPayload(array $pix): array
  168. {
  169. if (! self::filled($pix['expires_in'] ?? null) && ! self::filled($pix['expires_at'] ?? null)) {
  170. throw new \InvalidArgumentException('pix.expires_in ou pix.expires_at e obrigatorio.');
  171. }
  172. $payload = [];
  173. foreach (['expires_in', 'expires_at', 'additional_information'] as $field) {
  174. if (array_key_exists($field, $pix) && self::filled($pix[$field])) {
  175. $payload[$field] = $pix[$field];
  176. }
  177. }
  178. return $payload;
  179. }
  180. //
  181. private static function filled(mixed $value): bool
  182. {
  183. return $value !== null && $value !== '' && $value !== [];
  184. }
  185. private static function filterFilledRecursiveStatic(array $data): array
  186. {
  187. $filtered = [];
  188. foreach ($data as $key => $value) {
  189. if (is_array($value)) {
  190. $value = self::filterFilledRecursiveStatic($value);
  191. }
  192. if (self::filled($value)) {
  193. $filtered[$key] = $value;
  194. }
  195. }
  196. return $filtered;
  197. }
  198. private static function paymentMethodWithOptionalSplit(array $paymentMethod, ?array $split): array
  199. {
  200. if (! empty($split)) {
  201. $paymentMethod['split'] = $split;
  202. }
  203. return $paymentMethod;
  204. }
  205. private static function validateItems(array $items): array
  206. {
  207. return collect($items)
  208. ->map(function (array $item, int $index) {
  209. foreach (['code', 'amount', 'quantity'] as $field) {
  210. if (! array_key_exists($field, $item) || ! self::filled($item[$field])) {
  211. throw new \InvalidArgumentException("items.{$index}.{$field} e obrigatorio.");
  212. }
  213. }
  214. if ((int) $item['amount'] <= 0 || (int) $item['quantity'] <= 0) {
  215. throw new \InvalidArgumentException("items.{$index}.amount e quantity devem ser maiores que zero.");
  216. }
  217. return self::filterFilledRecursiveStatic($item);
  218. })
  219. ->values()
  220. ->all();
  221. }
  222. }