JFIFxxC      C  " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr{ gilour
<?php namespace Common\Billing; use App\Models\User; use Carbon\Carbon; use Common\Billing\Gateways\Contracts\CommonSubscriptionGatewayActions; use Common\Billing\Gateways\Paypal\Paypal; use Common\Billing\Gateways\Stripe\Stripe; use Common\Billing\Models\Price; use Common\Billing\Models\Product; use Common\Billing\Subscriptions\SubscriptionFactory; use Common\Core\BaseModel; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use LogicException; class Subscription extends BaseModel { use HasFactory; public const MODEL_TYPE = 'subscription'; protected $guarded = ['id']; protected $appends = [ 'on_grace_period', 'on_trial', 'valid', 'active', 'cancelled', ]; protected $casts = [ 'id' => 'integer', 'price_id' => 'integer', 'product_id' => 'integer', 'quantity' => 'integer', 'trial_ends_at' => 'datetime', 'ends_at' => 'datetime', 'renews_at' => 'datetime', ]; public function getOnGracePeriodAttribute(): bool { return $this->onGracePeriod(); } public function getOnTrialAttribute(): bool { return $this->onTrial(); } public function getValidAttribute(): bool { return $this->valid(); } public function getActiveAttribute(): bool { return $this->active(); } public function getCancelledAttribute(): bool { return $this->cancelled(); } public function user(): BelongsTo { return $this->belongsTo(User::class); } public function price(): BelongsTo { return $this->belongsTo(Price::class); } public function product(): BelongsTo { return $this->belongsTo(Product::class); } public function onTrial(): bool { if (!is_null($this->trial_ends_at)) { return Carbon::now()->lt($this->trial_ends_at); } else { return false; } } /** * Determine if the subscription is active, on trial, or within its grace period. */ public function valid(): bool { return $this->active() || $this->onTrial() || $this->onGracePeriod(); } public function active(): bool { return is_null($this->ends_at) || $this->onGracePeriod(); } /** * Determine if the subscription is no longer active. */ public function cancelled(): bool { return !is_null($this->ends_at); } /** * Determine if the subscription is within its grace period after cancellation. */ public function onGracePeriod(): bool { if (!is_null($endsAt = $this->ends_at)) { return Carbon::now()->lt(Carbon::instance($endsAt)); } else { return false; } } public function changePlan(Product $newProduct, Price $newPrice): self { $isSuccess = $this->gateway()?->changePlan( $this, $newProduct, $newPrice, ); if ($isSuccess) { $this->fill([ 'product_id' => $newProduct->id, 'price_id' => $newPrice->id, 'ends_at' => null, ])->save(); } return $this; } public function cancel(bool $atPeriodEnd = true): self { if ($this->gateway_name !== 'none') { $this->gateway()->cancelSubscription($this, $atPeriodEnd); } // If the user was on trial, we will set the grace period to end when the trial // would have ended. Otherwise, we'll retrieve the end of the billing period // and make that the end of the grace period for this current user. if ($this->onTrial()) { $this->ends_at = $this->trial_ends_at; } else { $this->ends_at = $this->renews_at; } $this->renews_at = null; $this->save(); return $this; } /** * Mark subscription as cancelled on local database * only, without interacting with payment gateway. */ public function markAsCancelled(): void { $this->fill([ 'ends_at' => $this->renews_at, 'renews_at' => null, ])->save(); } /** * Cancel the subscription immediately and delete it from database. */ public function cancelAndDelete(): self { $this->cancel(false); $this->delete(); $this->user->update([ 'card_last_four' => null, 'card_brand' => null, 'card_expires' => null, ]); return $this; } public function resume(): self { if (!$this->onGracePeriod()) { throw new LogicException( __( 'Unable to resume subscription that is not within grace period.', ), ); } if ($this->onTrial()) { $trialEnd = $this->trial_ends_at->getTimestamp(); } else { $trialEnd = 'now'; } // To resume the subscription we need to set the plan parameter on the Stripe // subscription object. This will force Stripe to resume this subscription // where we left off. Then, we'll set the proper trial ending timestamp. if ($this->gateway_name !== 'none') { $this->gateway()->resumeSubscription($this, [ 'trial_end' => $trialEnd, ]); } // Finally, we will remove the ending timestamp from the user's record in the // local database to indicate that the subscription is active again and is // no longer "cancelled". Then we will save this record in the database. $this->renews_at = $this->ends_at; $this->ends_at = null; $this->save(); return $this; } /** * Get gateway this subscription was created with. */ public function gateway(): ?CommonSubscriptionGatewayActions { if ($this->gateway_name === 'stripe') { return app(Stripe::class); } elseif ($this->gateway_name === 'paypal') { return app(Paypal::class); } return null; } public function toNormalizedArray(): array { return [ 'id' => $this->id, 'name' => $this->this->gateway_name, 'model_type' => self::MODEL_TYPE, ]; } public function toSearchableArray(): array { return [ 'id' => $this->id, 'product_id' => $this->product_id, 'price_id' => $this->price_id, 'gateway_name' => $this->gateway_name, 'user' => $this->user ? $this->user->getSearchableValues() : null, 'description' => $this->description, 'ends_at' => $this->ends_at, 'created_at' => $this->created_at->timestamp ?? '_null', 'updated_at' => $this->updated_at->timestamp ?? '_null', ]; } protected function makeAllSearchableUsing($query) { return $query->with(['user']); } public static function filterableFields(): array { return [ 'id', 'product_id', 'price_id', 'gateway_name', 'ends_at', 'created_at', 'updated_at', ]; } protected static function newFactory() { return SubscriptionFactory::new(); } public static function getModelTypeAttribute(): string { return self::MODEL_TYPE; } }