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 creditCardPaymentMethod(array $creditCard, ?array $split = null): array
  78. {
  79. return self::paymentMethodWithOptionalSplit([
  80. 'payment_method' => 'credit_card',
  81. 'credit_card' => self::buildCreditCardPayload($creditCard),
  82. ], $split);
  83. }
  84. public static function pixPaymentMethod(array $pix, ?array $split = null): array
  85. {
  86. return self::paymentMethodWithOptionalSplit([
  87. 'payment_method' => 'pix',
  88. 'pix' => self::buildPixPayload($pix),
  89. ], $split);
  90. }
  91. public static function splitFromTransfers(Collection $transfers): array
  92. {
  93. return $transfers
  94. ->filter(fn (PaymentSplit $split) => ! empty($split->gateway_transfer_target_reference))
  95. ->map(function (PaymentSplit $split) {
  96. return [
  97. 'amount' => self::amountInCents((float) $split->gross_amount),
  98. 'recipient_id' => $split->gateway_transfer_target_reference,
  99. 'type' => 'flat',
  100. 'options' => [
  101. 'charge_processing_fee' => false,
  102. 'charge_remainder_fee' => false,
  103. 'liable' => false,
  104. ],
  105. ];
  106. })
  107. ->values()
  108. ->all();
  109. }
  110. //
  111. private static function buildCreditCardPayload(array $creditCard): array
  112. {
  113. $payload = [];
  114. foreach ([
  115. 'installments',
  116. 'statement_descriptor',
  117. 'operation_type',
  118. 'recurrence_cycle',
  119. 'metadata',
  120. 'extended_limit_enabled',
  121. 'extended_limit_code',
  122. 'merchant_category_code',
  123. 'authentication',
  124. 'auto_recovery',
  125. 'payload',
  126. 'payment_type',
  127. 'funding_source',
  128. 'initiated_type',
  129. 'recurrence_model',
  130. 'channel',
  131. 'payment_origin',
  132. ] as $field) {
  133. if (array_key_exists($field, $creditCard) && self::filled($creditCard[$field])) {
  134. $payload[$field] = $creditCard[$field];
  135. }
  136. }
  137. $allowedCardOptions = ['card', 'card_id', 'card_token', 'network_token'];
  138. $provided = array_values(array_filter(
  139. $allowedCardOptions,
  140. static fn (string $field) => ! empty($creditCard[$field])
  141. ));
  142. if (count($provided) !== 1) {
  143. throw new \InvalidArgumentException('Informe exatamente uma opcao entre card, card_id, card_token ou network_token.');
  144. }
  145. $selected = $provided[0];
  146. $payload[$selected] = $creditCard[$selected];
  147. return $payload;
  148. }
  149. private static function buildPixPayload(array $pix): array
  150. {
  151. if (! self::filled($pix['expires_in'] ?? null) && ! self::filled($pix['expires_at'] ?? null)) {
  152. throw new \InvalidArgumentException('pix.expires_in ou pix.expires_at e obrigatorio.');
  153. }
  154. $payload = [];
  155. foreach (['expires_in', 'expires_at', 'additional_information'] as $field) {
  156. if (array_key_exists($field, $pix) && self::filled($pix[$field])) {
  157. $payload[$field] = $pix[$field];
  158. }
  159. }
  160. return $payload;
  161. }
  162. //
  163. public static function amountInCents(float $amount): int
  164. {
  165. return (int) round($amount * 100);
  166. }
  167. private static function filled(mixed $value): bool
  168. {
  169. return $value !== null && $value !== '' && $value !== [];
  170. }
  171. private static function filterFilledRecursiveStatic(array $data): array
  172. {
  173. $filtered = [];
  174. foreach ($data as $key => $value) {
  175. if (is_array($value)) {
  176. $value = self::filterFilledRecursiveStatic($value);
  177. }
  178. if (self::filled($value)) {
  179. $filtered[$key] = $value;
  180. }
  181. }
  182. return $filtered;
  183. }
  184. private static function paymentMethodWithOptionalSplit(array $paymentMethod, ?array $split): array
  185. {
  186. if (! empty($split)) {
  187. $paymentMethod['split'] = $split;
  188. }
  189. return $paymentMethod;
  190. }
  191. private static function validateItems(array $items): array
  192. {
  193. return collect($items)
  194. ->map(function (array $item, int $index) {
  195. foreach (['code', 'amount', 'quantity'] as $field) {
  196. if (! array_key_exists($field, $item) || ! self::filled($item[$field])) {
  197. throw new \InvalidArgumentException("items.{$index}.{$field} e obrigatorio.");
  198. }
  199. }
  200. if ((int) $item['amount'] <= 0 || (int) $item['quantity'] <= 0) {
  201. throw new \InvalidArgumentException("items.{$index}.amount e quantity devem ser maiores que zero.");
  202. }
  203. return self::filterFilledRecursiveStatic($item);
  204. })
  205. ->values()
  206. ->all();
  207. }
  208. //
  209. public function toArray(): array
  210. {
  211. return $this->filterFilledRecursive([
  212. 'code' => $this->code,
  213. 'items' => $this->items,
  214. 'payments' => $this->payments,
  215. 'closed' => $this->closed,
  216. 'metadata' => $this->metadata,
  217. 'customer_id' => $this->customerId,
  218. 'customer' => $this->customer,
  219. 'channel' => $this->channel,
  220. ]);
  221. }
  222. }