<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @method static where(string $string, string $uid)
* @method static count()
* @method static offset(mixed $start)
* @method static whereLike(string[] $array, mixed $search)
* @method static insert(array[] $subscriptions)
* @method static whereNull(string $string)
* @method static whereDate(string $string, string $toDateString)
* @property mixed options
* @property mixed status
* @property mixed end_at
* @property int|mixed end_by
* @property Carbon|mixed start_at
* @property bool|mixed|null current_period_ends_at
* @property mixed end_period_last_days
* @property mixed plan
* @property mixed payment_claimed
*/
class Subscription extends Model
{
const STATUS_NEW = 'new';
const STATUS_PENDING = 'pending';
const STATUS_ACTIVE = 'active';
const STATUS_ENDED = 'ended';
const STATUS_RENEW = 'renew';
/**
* The attributes that should be mutated to date.
*
* @var array
*/
protected $dates = [
'current_period_ends_at',
'start_at',
'end_at',
'created_at',
'updated_at',
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'uid',
'options',
'status',
'paid',
'current_period_ends_at',
'payment_claimed',
'start_at',
'end_at',
'end_period_last_days',
];
/**
* Indicates if the plan change should be prorated.
*
* @var bool
*/
protected $prorate = true;
/**
* Bootstrap any application services.
*/
public static function boot()
{
parent::boot();
// Create uid when creating list.
static::creating(function ($subscription) {
// Create new uid
$uid = uniqid();
while (self::where('uid', $uid)->count() > 0) {
$uid = uniqid();
}
$subscription->uid = $uid;
$subscription->options = json_encode([
'credit_warning' => true,
'credit' => '100',
'credit_notify' => 'both',
'subscription_warning' => true,
'subscription_notify' => 'both',
'send_warning' => false,
]);
});
}
/**
* Find item by uid.
*
* @param $uid
*
* @return object
*/
public static function findByUid($uid): object
{
return self::where('uid', $uid)->first();
}
/**
* Get options.
*
* @return mixed
*/
public function getOptions()
{
if ( ! $this->options) {
return json_decode('{}', true);
}
return json_decode($this->options, true);
}
/**
* get single option
*
* @param $name
*
* @return string
*/
public function getOption($name): string
{
return $this->getOptions()[$name];
}
/**
* update options
*
* @param $data
*
*/
public function updateOptions($data)
{
$options = (object) array_merge((array) $this->getOptions(), $data);
$this->options = json_encode($options);
$this->save();
}
/**
* plan associations
*
* @return BelongsTo
*
*/
public function plan(): BelongsTo
{
return $this->belongsTo(Plan::class, 'plan_id', 'id');
}
/**
* Get the user that owns the subscription.
*
* @return BelongsTo
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id', 'id');
}
/**
* subscription transactions associations
*
* @return HasMany
*/
public function subscriptionTransactions(): HasMany
{
return $this->hasMany(SubscriptionTransaction::class);
}
/**
* subscriptions logs associations
*
* @return HasMany
*/
public function subscriptionLogs(): HasMany
{
return $this->hasMany(SubscriptionLog::class);
}
/**
* Determine if the subscription is active.
*
* @return bool
*/
public function isActive(): bool
{
return $this->status == self::STATUS_ACTIVE;
}
/**
* Determine if the subscription is active.
*
* @return bool
*/
public function isNew(): bool
{
return $this->status == self::STATUS_NEW;
}
/**
* Determine if the subscription is renewing.
*
* @return bool
*/
public function isRenew(): bool
{
return $this->status == self::STATUS_RENEW;
}
/**
* Determine if the subscription is active.
*
* @return bool
*/
public function isPending(): bool
{
return $this->status == self::STATUS_PENDING;
}
/**
* Determine if the subscription is no longer active.
*
* @return bool
*/
public function cancelled(): bool
{
return ! is_null($this->end_at);
}
/**
* Determine if the subscription is ended.
*
* @return bool
*/
public function isEnded(): bool
{
return $this->status == self::STATUS_ENDED;
}
/**
* Cancel subscription. Set ends at to the end of period.
*
* @return void
*/
public function cancelNow()
{
$this->setEnded();
}
/**
* Determine if the subscription is ended.
*
* @param int $ended_by
*
* @return void
*/
public function setEnded(int $ended_by = 1)
{
$invoice = Invoices::whereLike(['transaction_id'], $this->uid)->first();
if ($invoice) {
$invoice->delete();
}
$this->status = self::STATUS_ENDED;
$this->end_by = $ended_by;
$this->current_period_ends_at = Carbon::now();
$this->end_at = Carbon::now();
$this->save();
}
/**
* Determine if the subscription is pending.
*
* @return void
*/
public function setPending()
{
$this->status = self::STATUS_PENDING;
$this->save();
}
/**
* Determine if the subscription is pending.
*
* @return void
*/
public function setActive()
{
$this->status = self::STATUS_ACTIVE;
$this->start_at = Carbon::now();
$this->current_period_ends_at = $this->getPeriodEndsAt(Carbon::now());
$this->save();
}
/**
* Determine if the subscription is active, on trial, or within its grace period.
*
* @return bool
*/
public function valid(): bool
{
return $this->active() || $this->onGracePeriod();
}
/**
* Determine if the subscription is active.
*
* @return bool
*/
public function active(): bool
{
return (is_null($this->end_at) || $this->onGracePeriod()) && ! $this->isPending() && ! $this->isNew();
}
/**
* Determine if the subscription is recurring and not on trial.
*
* @return bool
*/
public function isRecurring(): bool
{
return ! $this->cancelled();
}
/**
* Determine if the subscription is within its grace period after cancellation.
*
* @return bool
*/
public function onGracePeriod(): bool
{
return $this->current_period_ends_at && $this->current_period_ends_at->isFuture();
}
/**
* Check if subscription is going to expire.
*
* @return Boolean
*/
public function goingToExpire(): bool
{
if ( ! $this->end_at) {
return false;
}
$days = $this->end_period_last_days;
return $this->end_at->subDay($days)->lessThanOrEqualTo(Carbon::now());
}
/**
* Next one period to subscription.
*
* @return null
*/
public function nextPeriod()
{
$endsAt = $this->current_period_ends_at;
$interval = $this->plan->getBillableInterval();
$intervalCount = $this->plan->getBillableIntervalCount();
match ($interval) {
'month' => $endsAt->addMonthsNoOverflow($intervalCount),
'day' => $endsAt->addDay($intervalCount),
'week' => $endsAt->addWeek($intervalCount),
'year' => $endsAt->addYearsNoOverflow($intervalCount),
default => null,
};
return $endsAt;
}
/**
* Next one period to subscription.
*
* @return null
*/
public function periodStartAt()
{
$startAt = $this->current_period_ends_at;
$interval = $this->plan->getBillableInterval();
$intervalCount = $this->plan->getBillableIntervalCount();
match ($interval) {
'month' => $startAt->subMonthsNoOverflow($intervalCount),
'day' => $startAt->subDay($intervalCount),
'week' => $startAt->subWeek($intervalCount),
'year' => $startAt->subYearsNoOverflow($intervalCount),
default => null,
};
return $startAt;
}
/**
* Add one period to subscription.
*
* @return void
*/
public function addPeriod()
{
$this->end_at = $this->nextPeriod();
$this->save();
}
/**
* Check if payment is claimed.
*
* @return mixed
*/
public function isPaymentClaimed()
{
return $this->payment_claimed;
}
/**
* Claim payment.
*
* @return void
*/
public function claimPayment()
{
$this->payment_claimed = true;
$this->save();
}
/**
*
* @param $startDate
*
* @return null
*/
public function getPeriodEndsAt($startDate)
{
// does not support recurring, update ends at column
$interval = $this->plan->getBillableInterval();
$intervalCount = $this->plan->getBillableIntervalCount();
match ($interval) {
'month' => $startDate->addMonthsNoOverflow($intervalCount),
'day' => $startDate->addDay($intervalCount),
'week' => $startDate->addWeek($intervalCount),
'year' => $startDate->addYearsNoOverflow($intervalCount),
default => null,
};
return $startDate;
}
/**
* Start subscription.
*
* @return void
*/
public function start()
{
$this->end_at = null;
$this->current_period_ends_at = $this->getPeriodEndsAt(Carbon::now());
$this->status = self::STATUS_ACTIVE;
$this->start_at = Carbon::now();
$this->save();
}
/**
* Subscription transactions.
*
* @return Collection
*/
public function getTransactions(): Collection
{
return $this->subscriptionTransactions()->orderBy('created_at', 'desc')->get();
}
/**
* Subscription transactions.
*
* @return Collection
*/
public function getLogs(): Collection
{
return $this->subscriptionLogs()->orderBy('created_at', 'desc')->get();
}
/**
* Subscription transactions.
*
* @param $type
* @param $data
*
* @return SubscriptionTransaction
*/
public function addTransaction($type, $data): SubscriptionTransaction
{
$transaction = new SubscriptionTransaction();
$transaction->subscription_id = $this->id;
$transaction->type = $type;
$transaction->fill($data);
if (isset($data['options'])) {
$transaction->options = json_encode($data['options']);
}
$transaction->save();
return $transaction;
}
/**
* Subscription transactions.
*
* @param $type
* @param $data
* @param null $transaction_id
*
* @return SubscriptionLog
*/
public function addLog($type, $data, $transaction_id = null): SubscriptionLog
{
$log = new SubscriptionLog();
$log->subscription_id = $this->id;
$log->type = $type;
$log->transaction_id = $transaction_id;
$log->save();
if (isset($data)) {
$log->updateData($data);
}
return $log;
}
/**
* get route key by uid
*
* @return string
*/
public function getRouteKeyName(): string
{
return 'uid';
}
/**
* @return string
*/
public function __toString(): string
{
return $this->name;
}
}