152 lines
4.7 KiB
PHP
152 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use Carbon\CarbonImmutable;
|
|
use Illuminate\Http\Client\PendingRequest;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\Http;
|
|
use RuntimeException;
|
|
|
|
class RevenueCatService
|
|
{
|
|
public function sync(User $user): User
|
|
{
|
|
$response = $this->client()
|
|
->get('subscribers/'.rawurlencode((string) $user->getKey()))
|
|
->throw()
|
|
->json();
|
|
|
|
$subscriber = Arr::get($response, 'subscriber', []);
|
|
|
|
if (! is_array($subscriber)) {
|
|
throw new RuntimeException('RevenueCat returned an invalid subscriber payload.');
|
|
}
|
|
|
|
$subscription = $this->entitlement(
|
|
$subscriber,
|
|
(string) config('billing.revenuecat.subscription_entitlement'),
|
|
);
|
|
$certification = $this->entitlement(
|
|
$subscriber,
|
|
(string) config('billing.revenuecat.certification_entitlement'),
|
|
);
|
|
|
|
$subscriptionProductId = $this->stringValue($subscription['product_identifier'] ?? null);
|
|
$subscriptionDetails = $subscriptionProductId
|
|
? Arr::get($subscriber, "subscriptions.{$subscriptionProductId}", [])
|
|
: [];
|
|
|
|
$user->forceFill([
|
|
'subscription_product_id' => $this->entitlementIsActive($subscription)
|
|
? $subscriptionProductId
|
|
: null,
|
|
'subscription_store' => $this->entitlementIsActive($subscription)
|
|
? $this->stringValue(is_array($subscriptionDetails) ? ($subscriptionDetails['store'] ?? null) : null)
|
|
: null,
|
|
'subscription_expires_at' => $this->entitlementIsActive($subscription)
|
|
? $this->effectiveExpiration($subscription)
|
|
: null,
|
|
'subscription_is_trial' => $this->entitlementIsActive($subscription)
|
|
&& is_array($subscriptionDetails)
|
|
&& strtoupper((string) ($subscriptionDetails['period_type'] ?? '')) === 'TRIAL',
|
|
'certification_purchased_at' => $this->entitlementIsActive($certification)
|
|
? $this->dateValue($certification['purchase_date'] ?? null) ?? now()
|
|
: null,
|
|
])->save();
|
|
|
|
return $user->fresh();
|
|
}
|
|
|
|
public function deleteCustomer(User $user): void
|
|
{
|
|
if (trim((string) config('billing.revenuecat.secret_key')) === '') {
|
|
return;
|
|
}
|
|
|
|
$response = $this->client()
|
|
->delete('subscribers/'.rawurlencode((string) $user->getKey()));
|
|
|
|
if (! $response->notFound()) {
|
|
$response->throw();
|
|
}
|
|
}
|
|
|
|
private function client(): PendingRequest
|
|
{
|
|
$secretKey = trim((string) config('billing.revenuecat.secret_key'));
|
|
|
|
if ($secretKey === '') {
|
|
throw new RuntimeException('REVENUECAT_SECRET_KEY is not configured.');
|
|
}
|
|
|
|
return Http::baseUrl(rtrim((string) config('billing.revenuecat.api_url'), '/'))
|
|
->withToken($secretKey)
|
|
->acceptJson()
|
|
->connectTimeout(5)
|
|
->timeout(12)
|
|
->retry([200, 500], throw: false);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $subscriber
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function entitlement(array $subscriber, string $identifier): array
|
|
{
|
|
$entitlement = Arr::get($subscriber, "entitlements.{$identifier}", []);
|
|
|
|
return is_array($entitlement) ? $entitlement : [];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $entitlement
|
|
*/
|
|
private function entitlementIsActive(array $entitlement): bool
|
|
{
|
|
if ($entitlement === []) {
|
|
return false;
|
|
}
|
|
|
|
$expiration = $this->effectiveExpiration($entitlement);
|
|
|
|
return $expiration === null || $expiration->isFuture();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $entitlement
|
|
*/
|
|
private function effectiveExpiration(array $entitlement): ?CarbonImmutable
|
|
{
|
|
$expiration = $this->dateValue($entitlement['expires_date'] ?? null);
|
|
$gracePeriodExpiration = $this->dateValue($entitlement['grace_period_expires_date'] ?? null);
|
|
|
|
if ($expiration === null) {
|
|
return $gracePeriodExpiration;
|
|
}
|
|
|
|
if ($gracePeriodExpiration === null) {
|
|
return $expiration;
|
|
}
|
|
|
|
return $gracePeriodExpiration->greaterThan($expiration)
|
|
? $gracePeriodExpiration
|
|
: $expiration;
|
|
}
|
|
|
|
private function dateValue(mixed $value): ?CarbonImmutable
|
|
{
|
|
if (! is_string($value) || $value === '') {
|
|
return null;
|
|
}
|
|
|
|
return CarbonImmutable::parse($value);
|
|
}
|
|
|
|
private function stringValue(mixed $value): ?string
|
|
{
|
|
return is_string($value) && $value !== '' ? $value : null;
|
|
}
|
|
}
|