Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
91.49% |
43 / 47 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
| SyncSubscriptionFromWebhookAction | |
91.49% |
43 / 47 |
|
25.00% |
1 / 4 |
20.25 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| execute | |
97.14% |
34 / 35 |
|
0.00% |
0 / 1 |
10 | |||
| extractPreapprovalId | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
3.14 | |||
| mapStatus | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
6.84 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace ImovelZapAi\Billing\Application\Actions; |
| 6 | |
| 7 | use ImovelZapAi\Billing\Application\Contracts\MercadoPagoClient; |
| 8 | use ImovelZapAi\Billing\Infrastructure\Persistence\Eloquent\BillingPaymentEventModel; |
| 9 | use ImovelZapAi\Organizations\Domain\Enums\SubscriptionStatus; |
| 10 | use ImovelZapAi\Organizations\Infrastructure\Persistence\Eloquent\OrganizationSubscriptionModel; |
| 11 | |
| 12 | final class SyncSubscriptionFromWebhookAction |
| 13 | { |
| 14 | public function __construct( |
| 15 | private readonly MercadoPagoClient $client, |
| 16 | ) {} |
| 17 | |
| 18 | /** |
| 19 | * @param array<string, mixed> $payload |
| 20 | */ |
| 21 | public function execute(array $payload): ?OrganizationSubscriptionModel |
| 22 | { |
| 23 | $externalId = $this->extractPreapprovalId($payload); |
| 24 | $eventType = (string) ($payload['type'] ?? $payload['action'] ?? 'unknown'); |
| 25 | |
| 26 | $subscription = null; |
| 27 | if ($externalId !== null) { |
| 28 | $subscription = OrganizationSubscriptionModel::query() |
| 29 | ->where('mp_preapproval_id', $externalId) |
| 30 | ->first(); |
| 31 | |
| 32 | if ($subscription === null) { |
| 33 | $remote = $this->client->getPreapproval($externalId); |
| 34 | $ref = $remote['external_reference'] ?? null; |
| 35 | if (is_string($ref) && $ref !== '') { |
| 36 | $subscription = OrganizationSubscriptionModel::query()->find($ref); |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | if ($subscription !== null) { |
| 41 | $remote = $this->client->getPreapproval($externalId); |
| 42 | $status = $this->mapStatus((string) ($remote['status'] ?? '')); |
| 43 | |
| 44 | $subscription->forceFill([ |
| 45 | 'mp_preapproval_id' => $externalId, |
| 46 | 'status' => $status, |
| 47 | 'canceled_at' => $status === SubscriptionStatus::Canceled ? now() : null, |
| 48 | 'grace_ends_at' => $status === SubscriptionStatus::PastDue |
| 49 | ? now()->addDays((int) config('billing.grace_days', 3)) |
| 50 | : null, |
| 51 | 'current_period_ends_at' => $status === SubscriptionStatus::Active |
| 52 | ? ((string) ($subscription->billing_cycle ?? 'monthly') === 'yearly' ? now()->addYear() : now()->addMonth()) |
| 53 | : $subscription->current_period_ends_at, |
| 54 | ])->save(); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | BillingPaymentEventModel::query()->create([ |
| 59 | 'tenant_id' => $subscription?->tenant_id, |
| 60 | 'organization_subscription_id' => $subscription?->id, |
| 61 | 'provider' => 'mercadopago', |
| 62 | 'event_type' => $eventType, |
| 63 | 'external_id' => $externalId, |
| 64 | 'status' => $subscription?->status?->value, |
| 65 | 'payload' => $payload, |
| 66 | ]); |
| 67 | |
| 68 | return $subscription?->fresh(['plan']); |
| 69 | } |
| 70 | |
| 71 | /** |
| 72 | * @param array<string, mixed> $payload |
| 73 | */ |
| 74 | private function extractPreapprovalId(array $payload): ?string |
| 75 | { |
| 76 | $dataId = $payload['data']['id'] ?? $payload['id'] ?? null; |
| 77 | if (is_string($dataId) || is_numeric($dataId)) { |
| 78 | return (string) $dataId; |
| 79 | } |
| 80 | |
| 81 | return null; |
| 82 | } |
| 83 | |
| 84 | private function mapStatus(string $mpStatus): SubscriptionStatus |
| 85 | { |
| 86 | return match (strtolower($mpStatus)) { |
| 87 | 'authorized', 'active' => SubscriptionStatus::Active, |
| 88 | 'pending' => SubscriptionStatus::Trialing, |
| 89 | 'paused' => SubscriptionStatus::PastDue, |
| 90 | 'cancelled', 'canceled' => SubscriptionStatus::Canceled, |
| 91 | default => SubscriptionStatus::PastDue, |
| 92 | }; |
| 93 | } |
| 94 | } |