| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- <?php
- namespace App\Services;
- use App\Models\Payment;
- use App\Models\Webhook;
- use App\Services\Pagarme\PagarmePaymentService;
- use Illuminate\Support\Facades\Log;
- class WebhookService
- {
- private const PAYMENT_EVENTS = [
- 'order.paid',
- 'order.payment_failed',
- 'order.canceled',
- 'order.created',
- 'order.closed',
- 'order.updated',
- 'charge.created',
- 'charge.paid',
- 'charge.payment_failed',
- 'charge.refunded',
- 'charge.pending',
- 'charge.processing',
- 'charge.underpaid',
- 'charge.overpaid',
- 'charge.partial_canceled',
- 'charge.chargedback',
- 'charge.updated',
- ];
- public function __construct(
- private readonly PagarmePaymentService $pagarmePaymentService,
- private readonly PaymentService $paymentService,
- private readonly ProviderWithdrawalService $providerWithdrawalService,
- ) {}
- public function handlePagarme(array $payload): ?Payment
- {
- $event = $payload['type'] ?? null;
- $data = $payload['data'] ?? [];
- $data = is_array($data) ? $data : [];
- $webhook = $this->recordWebhook($payload, $event, $data);
- Log::channel('pagarme')->info('Pagar.me webhook received', [
- 'hook_id' => $payload['id'] ?? null,
- 'event' => $event,
- ]);
- if (! in_array($event, self::PAYMENT_EVENTS, true)) {
- if (str_starts_with((string) $event, 'transfer.')) {
- $this->providerWithdrawalService->handleTransferWebhook($data);
- $webhook->update([
- 'status' => 'processed',
- 'processed_at' => now(),
- ]);
- return null;
- }
- $webhook->update([
- 'status' => 'ignored',
- 'processed_at' => now(),
- ]);
- return null;
- }
- $orderResponse = $this->normalizeOrderResponse($event, $data);
- $payment = $this->findPayment($orderResponse);
- if (! $payment) {
- $webhook->update([
- 'status' => 'payment_not_found',
- 'processed_at' => now(),
- ]);
- Log::channel('pagarme')->warning('Payment not found for Pagar.me webhook', [
- 'hook_id' => $payload['id'] ?? null,
- 'event' => $event,
- 'order_id' => $orderResponse['id'] ?? null,
- 'charge_id' => $orderResponse['charges'][0]['id'] ?? null,
- 'transaction_id' => $orderResponse['charges'][0]['last_transaction']['id'] ?? null,
- 'metadata' => $orderResponse['metadata'] ?? [],
- ]);
- return null;
- }
- try {
- $payment = $this->pagarmePaymentService->applyGatewayResponseToPayment($payment, $orderResponse);
- $this->paymentService->syncScheduleStatusAfterPayment($payment->schedule, $payment);
- $webhook->update([
- 'payment_id' => $payment->id,
- 'status' => 'processed',
- 'processed_at' => now(),
- ]);
- return $payment;
- } catch (\Throwable $e) {
- $webhook->update([
- 'payment_id' => $payment->id,
- 'status' => 'failed',
- 'processed_at' => now(),
- 'error_message' => $e->getMessage(),
- ]);
- throw $e;
- }
- }
- private function recordWebhook(array $payload, ?string $event, array $data): Webhook
- {
- $references = $this->extractReferences($event, $data);
- $hookId = $payload['id'] ?? null;
- $attributes = [
- 'hook_id' => $hookId,
- 'provider' => 'pagarme',
- 'event' => $event,
- 'account_id' => $payload['account']['id'] ?? null,
- 'order_id' => $references['order_id'],
- 'charge_id' => $references['charge_id'],
- 'transaction_id' => $references['transaction_id'],
- 'status' => 'received',
- 'payload' => $payload,
- 'received_at' => now(),
- 'processed_at' => null,
- 'error_message' => null,
- ];
- if (empty($hookId)) {
- return Webhook::create($attributes);
- }
- $webhook = Webhook::firstOrNew([
- 'provider' => 'pagarme',
- 'hook_id' => $hookId,
- ]);
- $webhook->fill([
- ...$attributes,
- 'attempts_count' => $webhook->exists ? $webhook->attempts_count + 1 : 1,
- ])->save();
- return $webhook;
- }
- private function extractReferences(?string $event, array $data): array
- {
- if (str_starts_with((string) $event, 'order.')) {
- $charge = $data['charges'][0] ?? [];
- return [
- 'order_id' => $data['id'] ?? null,
- 'charge_id' => $charge['id'] ?? null,
- 'transaction_id' => $charge['last_transaction']['id'] ?? null,
- ];
- }
- if (str_starts_with((string) $event, 'charge.')) {
- return [
- 'order_id' => $data['order']['id'] ?? $data['order_id'] ?? null,
- 'charge_id' => $data['id'] ?? null,
- 'transaction_id' => $data['last_transaction']['id'] ?? null,
- ];
- }
- return [
- 'order_id' => null,
- 'charge_id' => null,
- 'transaction_id' => null,
- ];
- }
- private function normalizeOrderResponse(?string $event, array $data): array
- {
- if (str_starts_with((string) $event, 'order.')) {
- return $data;
- }
- return [
- 'id' => $data['order']['id'] ?? $data['order_id'] ?? null,
- 'metadata' => $data['metadata'] ?? $data['order']['metadata'] ?? [],
- 'charges' => [$data],
- ];
- }
- private function findPayment(array $orderResponse): ?Payment
- {
- $charge = $orderResponse['charges'][0] ?? [];
- $transaction = $charge['last_transaction'] ?? [];
- $metadata = $orderResponse['metadata'] ?? $charge['metadata'] ?? [];
- $metadataId = filter_var($metadata['payment_id'] ?? null, FILTER_VALIDATE_INT) ?: null;
- $chargeId = $charge['id'] ?? null;
- $transactionId = $transaction['id'] ?? null;
- $orderId = $orderResponse['id'] ?? null;
- $references = array_filter([$metadataId, $chargeId, $transactionId, $orderId]);
- if (empty($references)) {
- return null;
- }
- return Payment::query()
- ->where('gateway_provider', 'pagarme')
- ->where(function ($query) use ($metadataId, $chargeId, $transactionId, $orderId) {
- $query
- ->when($metadataId, fn ($query) => $query->orWhere('id', $metadataId))
- ->when($chargeId, fn ($query) => $query->orWhere('gateway_entity_reference', $chargeId))
- ->when($transactionId, fn ($query) => $query->orWhere('gateway_operation_reference', $transactionId))
- ->when($orderId, fn ($query) => $query->orWhere(function ($query) use ($orderId) {
- $query->where('gateway_entity_label', 'order')
- ->where('gateway_entity_reference', $orderId);
- }));
- })
- ->latest('id')
- ->first();
- }
- }
|