You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Post-v1.1.0 hardening
PHP constraint relaxed: composer.json"php" is now "^8.3 || ^8.4"
(was "^8.4"). The codebase uses zero PHP 8.4-specific features; the previous
constraint artificially blocked PHP 8.3 deployments.
Trial as first-class state: new SubscriptionStatus::Trialing enum case
with transitions Pending → Trialing and Trialing → {Active, PastDue, Cancelled, Failed}. Seven raw 'trialing' strings in production code
(SubscriptionService, PaytrProvider, PaymentChargeJob, ProcessRenewalCandidateJob, LicenseManager) replaced with the enum so Subscription::transitionTo()'s state-machine guard actually fires.
Trial orchestration: new plans.trial_days column, trial-aware SubscriptionService::create() (sets status=trialing and trial_ends_at
when trial_days > 0), new ProcessTrialExpiryJob, and new subguard:process-trial-expiry Artisan command (uses lazyById(500)).
Self-managed providers transition trialing → past_due on trial end;
provider-managed (iyzico) defers to its own subscription event.
Trial application hook: new Events\TrialEnding event fired by ProcessTrialExpiryJob (self-managed only) before the default Trialing → PastDue transition. Consuming applications can listen and
decide the trial-end policy (auto-charge, email confirmation, extend,
convert to free tier, etc.). If a sync listener transitions the
subscription out of Trialing, the package's PastDue default is
skipped. See docs/DOMAIN-BILLING.md for examples.
Idempotency hash safety: new Support\Json::safeHash() helper using JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE, replacing hash('sha256', (string) json_encode(\$x)) at three webhook eventId
fallback sites (IyzicoProvider::eventId, PaytrProvider::processWebhook, IyzicoProviderEventDispatcher::dispatch). Falls back to serialize()
on JsonException.
Chunked cron queries: processRenewals, processDunning, processScheduledPlanChanges, and retryPastDuePayments now stream
via lazyById(500) — peak memory is bounded regardless of backlog size.
Cancel TOCTOU defensive refresh: SubscriptionService::cancel()
re-reads the subscription after the cache lock attempt (both on
contention-loss and after acquiring the lock) and returns true
idempotently if a concurrent worker has cancelled the subscription.
PaymentCallbackController header filtering: 3DS and checkout
callbacks now strip authorization, cookie, set-cookie, and proxy-authorization headers before persisting to webhook_calls,
mirroring WebhookController::filterHeaders().
Documentation
INSTALLATION.md: corrected the iyzico IYZICO_CALLBACK_URL example
(was the non-existent /subguard/payment/iyzico/callback; now the
correct base /subguard/webhooks/iyzico with a note that the package
appends /3ds/callback and /checkout/callback).
Deferred to v1.3.0 (evidence required before merging)
DB::afterCommit event dispatch refactor (13 sites in SubscriptionService)
— needs a listener audit to confirm none rely on pre-commit visibility.
Iyzico cancel idempotency (subscription not found / already cancelled
error code mapping) — needs verification against vendor/iyzico/iyzipay-php or a live sandbox round-trip.