# fluent-cart/1.5.1/app/Services/Payments/SubscriptionHelper.php

FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler, version 1.5.1. 203 lines.

- Page: https://pluginprobe.com/plugins/fluent-cart/1.5.1/code/app/Services/Payments/SubscriptionHelper.php
- Raw: https://pluginprobe.com/plugins/fluent-cart/1.5.1/raw/app/Services/Payments/SubscriptionHelper.php
- Modified: 2026-06-11T12:21:58+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-cart/1.5.1/code/app/Services/Payments/SubscriptionHelper.php#L10-L20`.

```php
<?php

namespace FluentCart\App\Services\Payments;

use FluentCart\App\App;
use FluentCart\App\Helpers\Helper;
use FluentCart\App\Helpers\Status;
use FluentCart\App\Models\Order;
use FluentCart\App\Models\Subscription;
use FluentCart\App\Services\DateTime\DateTime;
use FluentCart\Framework\Support\Arr;

class SubscriptionHelper
{
    /*
     * @param $subscriptionModel
     * @return string|null
     *
     * */
    public static function getNextBillingDate(Subscription $subscriptionModel)
    {
        // only null case
        if ($subscriptionModel->status === Status::SUBSCRIPTION_COMPLETED || ($subscriptionModel->bill_times > 0 && $subscriptionModel->bill_count >= $subscriptionModel->bill_times)) {
            return null;
        }

        // assuming on expired we update the canceled_at, removes this comment when verified
        if ($subscriptionModel->status === Status::SUBSCRIPTION_CANCELED || $subscriptionModel->status === Status::SUBSCRIPTION_EXPIRED) {
            return $subscriptionModel->canceled_at;
        }

        // Trial handling
        if ($subscriptionModel->bill_count == 0 && !empty($subscriptionModel->trial_days)) {
            if (!empty($subscriptionModel->trial_ends_at)) {
                return $subscriptionModel->trial_ends_at;
            }
            return gmdate('Y-m-d H:i:s', strtotime($subscriptionModel->created_at . " +{$subscriptionModel->trial_days} days"));
        }

        if (!empty($subscriptionModel->next_billing_date) && strtotime($subscriptionModel->next_billing_date) > time()) {
            return $subscriptionModel->next_billing_date;
        }


        if ($subscriptionModel->bill_count == 0) {
            $baseDate = $subscriptionModel->created_at;

        } elseif (!empty($subscriptionModel->next_billing_date) && strtotime($subscriptionModel->next_billing_date) < time()) {
            $baseDate = $subscriptionModel->next_billing_date;
        } else {
            $baseDate = DateTime::gmtNow()->format('Y-m-d H:i:s');
        }

        switch (strtolower($subscriptionModel->billing_interval)) {
            case 'daily':
                $next = strtotime($baseDate . " +1 day");
                break;
            case 'weekly':
                $next = strtotime($baseDate . " +1 week");
                break;
            case 'monthly':
                $next = strtotime($baseDate . " +1 month");
                break;
            case 'quarterly':
                $next = strtotime($baseDate . " +3 months");
                break;
            case 'half_yearly':
                $next = strtotime($baseDate . " +6 months");
                break;
            case 'yearly':
                $next = strtotime($baseDate . " +1 year");
                break;
            default:
                $next = strtotime($baseDate . " +1 month"); // fallback
        }

        return gmdate('Y-m-d H:i:s', $next);
    }

    /*
     * @param $trialDays
     * @param $billTimes
     * @param $interval
     *
     * */
    public static function getSubscriptionCancelAtTimeStamp($trialDays, $billTimes, $interval)
    {
        if (!$billTimes && !$trialDays) {
            return null;
        }

        // Use the passed arguments instead of accessing non-existent $this->subscription
        if ($interval == 'daily') {
            $interval = 'day';
        }

        $interValMaps = [
            'day'     => 'days',
            'weekly'  => 'weeks',
            'monthly' => 'months',
            'yearly'  => 'years'
        ];

        if (isset($interValMaps[$interval]) && $billTimes > 0) {
            $interval = $interValMaps[$interval];
        }

        $timestamp = strtotime('+ ' . $billTimes . ' ' . $interval);

        // Add trial days if provided
        if ($trialDays > 0) {
            $timestamp = $timestamp + $trialDays * 24 * 60 * 60; // Add trial days in seconds
        }

        return $timestamp;
    }


    // can be used to catch 1 day trial loop-hole
    public static function checkTrailDaysLoopHole($subscription, $trialDays)
    {
        $billCount = Arr::get($subscription, 'bill_count');
        $billingInterval = Arr::get($subscription, 'billing_interval');
        $billingIntervalInDays = 0;
        switch ($billingInterval) {
            case 'monthly':
                $billingIntervalInDays = 30;
                break;
            case 'quarterly':
                $billingIntervalInDays = 90;
                break;
            case 'half_yearly':
                $billingIntervalInDays = 182;
                break;
            case 'yearly':
                $billingIntervalInDays = 365;
                break;
            case 'weekly':
                $billingIntervalInDays = 7;
                break;
            case 'daily':
                $billingIntervalInDays = 1;
                break;
        }

        // get the days from now to the created at date - original trial days,
        $daysSinceCreated = ceil(ceil((time() - strtotime($subscription->created_at)) / 86400)) - intval($subscription->trial_days);
        $expectedBillCount = floor($daysSinceCreated / $billingIntervalInDays);

        if ($expectedBillCount > $billCount) {
            $trialDays = 0;
        }

        return $trialDays;
    }

    /**
     * Safely convert a date string or Unix timestamp to a GMT datetime string.
     * Returns null when the value is falsy, zero, or a negative timestamp
     * (guards against strtotime() returning false or a year-0 negative value).
     *
     * @param string|int|null $value
     * @return string|null
     */
    public static function safeTimestampToDatetime($value): ?string
    {
        if (!$value) {
            return null;
        }
        $ts = is_numeric($value) ? (int) $value : strtotime($value);
        if (!$ts || $ts <= 0) {
            return null;
        }
        return gmdate('Y-m-d H:i:s', $ts);
    }

    public static function getSubscriptionsGracePeriodDays()
    {
        $defaults = [
            'daily'       => 1,
            'weekly'      => 3,
            'monthly'     => 7,
            'quarterly'   => 15,
            'half_yearly' => 15,
            'yearly'      => 15,
        ];

        $gracePeriods = apply_filters('fluent_cart/subscription/grace_period_days', $defaults);

        if (!is_array($gracePeriods)) {
            $gracePeriods = [];
        }

        foreach ($defaults as $interval => $defaultDays) {
            $days = $gracePeriods[$interval] ?? $defaultDays;
            $gracePeriods[$interval] = is_numeric($days) ? max(0, (int) $days) : $defaultDays;
        }

        return array_intersect_key($gracePeriods, $defaults);
    }

}

```
