tipo: modificación del cpanel y gitignore de la carpeta vendor
This commit is contained in:
+967
@@ -0,0 +1,967 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Paddle;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DateTimeInterface;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use InvalidArgumentException;
|
||||
use Laravel\Paddle\Concerns\Prorates;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
* @property \Laravel\Paddle\Billable $billable
|
||||
*/
|
||||
class Subscription extends Model
|
||||
{
|
||||
use Prorates;
|
||||
|
||||
const STATUS_ACTIVE = 'active';
|
||||
const STATUS_TRIALING = 'trialing';
|
||||
const STATUS_PAST_DUE = 'past_due';
|
||||
const STATUS_PAUSED = 'paused';
|
||||
const STATUS_CANCELED = 'canceled';
|
||||
|
||||
const INTERVAL_DAY = 'day';
|
||||
const INTERVAL_WEEK = 'week';
|
||||
const INTERVAL_MONTH = 'month';
|
||||
const INTERVAL_YEAR = 'year';
|
||||
|
||||
const DEFAULT_TYPE = 'default';
|
||||
|
||||
/**
|
||||
* The attributes that are not mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* The relations to eager load on every query.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $with = ['items'];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'trial_ends_at' => 'datetime',
|
||||
'paused_at' => 'datetime',
|
||||
'ends_at' => 'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the billable model related to the subscription.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
|
||||
*/
|
||||
public function billable()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription items related to the subscription.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function items()
|
||||
{
|
||||
return $this->hasMany(Cashier::$subscriptionItemModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription item for the given price.
|
||||
*
|
||||
* @param string $price
|
||||
* @return \Laravel\Paddle\SubscriptionItem
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
*/
|
||||
public function findItemOrFail($price)
|
||||
{
|
||||
return $this->items()->where('price_id', $price)->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a specific item by price or the single item on a subscription.
|
||||
*
|
||||
* @param string|null $price
|
||||
* @return \Laravel\Paddle\SubscriptionItem
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function singleItemOrFail($price = null)
|
||||
{
|
||||
if ($this->items()->count() > 1 && is_null($price)) {
|
||||
throw new InvalidArgumentException(
|
||||
'Please provide a price when retrieving an item of a subscription with multiple prices.'
|
||||
);
|
||||
}
|
||||
|
||||
return $price ? $this->findItemOrFail($price) : $this->items()->firstOrFail();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all of the transactions for the Billable model.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function transactions()
|
||||
{
|
||||
return $this->hasMany(Cashier::$transactionModel, 'paddle_subscription_id', 'paddle_id')
|
||||
->orderByDesc('created_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription has multiple prices.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasMultiplePrices()
|
||||
{
|
||||
return $this->items->count() > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription has a single price.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSinglePrice()
|
||||
{
|
||||
return ! $this->hasMultiplePrices();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription has a specific product.
|
||||
*
|
||||
* @param string $product
|
||||
* @return bool
|
||||
*/
|
||||
public function hasProduct($product)
|
||||
{
|
||||
return $this->items->contains(function (SubscriptionItem $item) use ($product) {
|
||||
return $item->product_id === $product;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription has a specific price.
|
||||
*
|
||||
* @param string $price
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPrice($price)
|
||||
{
|
||||
return $this->items->contains(function (SubscriptionItem $item) use ($price) {
|
||||
return $item->price_id === $price;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is active, on trial, or within its grace period.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function valid()
|
||||
{
|
||||
return $this->onTrial() || $this->active() || (! Cashier::$deactivatePastDue && $this->pastDue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by valid.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeValid($query)
|
||||
{
|
||||
$query->where('status', self::STATUS_TRIALING)
|
||||
->orWhere('status', self::STATUS_ACTIVE);
|
||||
|
||||
if (! Cashier::$deactivatePastDue) {
|
||||
$query->orWhere('status', self::STATUS_PAST_DUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is within its trial period.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onTrial()
|
||||
{
|
||||
return $this->status === self::STATUS_TRIALING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by on trial.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeOnTrial($query)
|
||||
{
|
||||
$query->where('status', self::STATUS_TRIALING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription's trial has expired.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExpiredTrial()
|
||||
{
|
||||
return $this->trial_ends_at && $this->trial_ends_at->isPast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by expired trial.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeExpiredTrial($query)
|
||||
{
|
||||
$query->whereNotNull('trial_ends_at')->where('trial_ends_at', '<', Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by not on trial.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeNotOnTrial($query)
|
||||
{
|
||||
$query->where('status', '!=', self::STATUS_TRIALING);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function active()
|
||||
{
|
||||
return $this->status === self::STATUS_ACTIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by active.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeActive($query)
|
||||
{
|
||||
$query->where('status', '=', self::STATUS_ACTIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is active and not on any grace period.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function recurring()
|
||||
{
|
||||
return $this->active() && ! $this->onPausedGracePeriod() && ! $this->onGracePeriod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by recurring.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeRecurring($query)
|
||||
{
|
||||
$query->active()->notOnPausedGracePeriod()->notOnGracePeriod();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is past due.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function pastDue()
|
||||
{
|
||||
return $this->status === self::STATUS_PAST_DUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by past due.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopePastDue($query)
|
||||
{
|
||||
$query->where('status', self::STATUS_PAST_DUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is paused.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function paused()
|
||||
{
|
||||
return $this->status === self::STATUS_PAUSED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by paused.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopePaused($query)
|
||||
{
|
||||
$query->where('status', self::STATUS_PAUSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by not paused.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeNotPaused($query)
|
||||
{
|
||||
$query->where('status', '!=', self::STATUS_PAUSED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is within its grace period after being paused.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onPausedGracePeriod()
|
||||
{
|
||||
return $this->paused_at && $this->paused_at->isFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by on trial grace period.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeOnPausedGracePeriod($query)
|
||||
{
|
||||
$query->whereNotNull('paused_at')->where('paused_at', '>', Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by not on trial grace period.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeNotOnPausedGracePeriod($query)
|
||||
{
|
||||
$query->whereNull('paused_at')->orWhere('paused_at', '<=', Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is no longer active.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canceled()
|
||||
{
|
||||
return $this->status === self::STATUS_CANCELED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by canceled.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeCanceled($query)
|
||||
{
|
||||
$query->where('status', self::STATUS_CANCELED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by not canceled.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeNotCanceled($query)
|
||||
{
|
||||
$query->where('status', '!=', self::STATUS_CANCELED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the subscription is within its grace period after cancellation.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function onGracePeriod()
|
||||
{
|
||||
return $this->ends_at && $this->ends_at->isFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by on grace period.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeOnGracePeriod($query)
|
||||
{
|
||||
$query->whereNotNull('ends_at')->where('ends_at', '>', Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter query by not on grace period.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return void
|
||||
*/
|
||||
public function scopeNotOnGracePeriod($query)
|
||||
{
|
||||
$query->whereNull('ends_at')->orWhere('ends_at', '<=', Carbon::now());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bill for one-time charges on top of the subscription.
|
||||
*
|
||||
* @param string|array $items
|
||||
* @param bool $chargeNow
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function charge($items, bool $chargeNow = false)
|
||||
{
|
||||
if (empty($items = (array) $items)) {
|
||||
throw new InvalidArgumentException('Please provide at least one item when charging one-time.');
|
||||
}
|
||||
|
||||
$response = Cashier::api('POST', "subscriptions/{$this->paddle_id}/charge", [
|
||||
'effective_from' => $chargeNow ? 'immediately' : 'next_billing_period',
|
||||
'items' => Cashier::normalizeItems($items),
|
||||
])['data'];
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
])->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bill for one-time charges on top of the subscription, and invoice immediately.
|
||||
*
|
||||
* @param string|array $items
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function chargeAndInvoice($items)
|
||||
{
|
||||
$this->setProrateAndInvoice('chargeAndInvoice');
|
||||
|
||||
return $this->charge($items, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the quantity of a subscription item.
|
||||
*
|
||||
* @param int $count
|
||||
* @param string|null $price
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function incrementQuantity($count = 1, $price = null)
|
||||
{
|
||||
$item = $this->singleItemOrFail($price);
|
||||
|
||||
return $this->updateQuantity($item->quantity + $count, $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the quantity of the subscription, and invoice immediately.
|
||||
*
|
||||
* @param int $count
|
||||
* @param string|null $price
|
||||
* @return $this
|
||||
*/
|
||||
public function incrementAndInvoice($count = 1, $price = null)
|
||||
{
|
||||
$item = $this->singleItemOrFail($price);
|
||||
|
||||
$this->setProrateAndInvoice('incrementAndInvoice');
|
||||
|
||||
return $this->updateQuantity($item->quantity + $count, $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the quantity of a subscription item.
|
||||
*
|
||||
* @param int $count
|
||||
* @param string|null $price
|
||||
* @return $this
|
||||
*/
|
||||
public function decrementQuantity($count = 1, $price = null)
|
||||
{
|
||||
$item = $this->singleItemOrFail($price);
|
||||
|
||||
return $this->updateQuantity(max(1, $item->quantity - $count), $item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the quantity of the subscription.
|
||||
*
|
||||
* @param int $quantity
|
||||
* @param \Laravel\Paddle\SubscriptionItem|string|null $price
|
||||
* @return $this
|
||||
*/
|
||||
public function updateQuantity($quantity, $price = null)
|
||||
{
|
||||
if ($quantity < 1) {
|
||||
throw new LogicException('Quantities of zero are not allowed.');
|
||||
}
|
||||
|
||||
$itemToUpdate = $price instanceof SubscriptionItem ? $price : $this->singleItemOrFail($price);
|
||||
|
||||
$items = $this->items()
|
||||
->get(['quantity', 'price_id'])
|
||||
->map(function ($item) {
|
||||
return [
|
||||
'price_id' => $item['price_id'],
|
||||
'quantity' => $item['quantity'],
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
|
||||
foreach ($items as $key => $item) {
|
||||
if ($item['price_id'] === $itemToUpdate->price_id) {
|
||||
$items[$key]['quantity'] = $quantity;
|
||||
}
|
||||
}
|
||||
|
||||
$response = $this->updatePaddleSubscription([
|
||||
'items' => $items,
|
||||
'proration_billing_mode' => $this->prorationBehavior,
|
||||
]);
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
])->save();
|
||||
|
||||
$itemToUpdate->forceFill([
|
||||
'quantity' => $quantity,
|
||||
])->save();
|
||||
|
||||
$this->load('items');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the trial period of the subscription.
|
||||
*
|
||||
* @param \DateTimeInterface|string $until
|
||||
* @return $this
|
||||
*/
|
||||
public function extendTrial($until)
|
||||
{
|
||||
$response = $this->updatePaddleSubscription([
|
||||
'next_billed_at' => Carbon::parse($until)->format(DateTimeInterface::RFC3339),
|
||||
'proration_billing_mode' => 'do_not_bill',
|
||||
]);
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
'trial_ends_at' => Carbon::parse($response['next_billed_at'], 'UTC'),
|
||||
])->save();
|
||||
|
||||
$this->syncSubscriptionItems($response['items']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the trial to end immediately and activate the subscription.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function activate()
|
||||
{
|
||||
$response = Cashier::api('POST', "subscriptions/{$this->paddle_id}/activate")['data'];
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
'trial_ends_at' => null,
|
||||
])->save();
|
||||
|
||||
$this->syncSubscriptionItems($response['items']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the subscription to new Paddle items.
|
||||
*
|
||||
* @param string|array $items
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function swap($items, array $options = [])
|
||||
{
|
||||
if (empty($items = (array) $items)) {
|
||||
throw new InvalidArgumentException('Please provide at least one item when swapping.');
|
||||
}
|
||||
|
||||
$items = Cashier::normalizeItems($items);
|
||||
|
||||
$response = $this->updatePaddleSubscription(array_merge($options, [
|
||||
'items' => $items,
|
||||
'proration_billing_mode' => $this->prorationBehavior,
|
||||
]));
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
])->save();
|
||||
|
||||
$this->syncSubscriptionItems($response['items']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the subscription to a new Paddle plan, and invoice immediately.
|
||||
*
|
||||
* @param string|array $items
|
||||
* @param array $options
|
||||
* @return $this
|
||||
*/
|
||||
public function swapAndInvoice($items, array $options = [])
|
||||
{
|
||||
$this->setProrateAndInvoice('swapAndInvoice');
|
||||
|
||||
return $this->swap($items, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the billing cycle anchor.
|
||||
*
|
||||
* @param \DateTimeInterface|string|null $date
|
||||
* @return $this
|
||||
*/
|
||||
public function anchorBillingCycleOn($date)
|
||||
{
|
||||
$this->updatePaddleSubscription([
|
||||
'next_billed_at' => Carbon::parse($date)->format(DateTimeInterface::RFC3339),
|
||||
'proration_billing_mode' => $this->prorationBehavior,
|
||||
]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user to the Paddle payment method update URL.
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function redirectToUpdatePaymentMethod()
|
||||
{
|
||||
return redirect($this->paymentMethodUpdateUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Paddle payment method update URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function paymentMethodUpdateUrl()
|
||||
{
|
||||
return Cashier::api('GET', "subscriptions/{$this->paddle_id}")['data']['management_urls']['update_payment_method'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw transaction used to update the payment method on file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function paymentMethodUpdateTransaction()
|
||||
{
|
||||
return Cashier::api('GET', "subscriptions/{$this->paddle_id}/update-payment-method-transaction")['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect the user to the Paddle cancel URL.
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function redirectToCancel()
|
||||
{
|
||||
return redirect($this->cancelUrl());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Paddle cancel URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function cancelUrl()
|
||||
{
|
||||
return Cashier::api('GET', "subscriptions/{$this->paddle_id}")['data']['management_urls']['cancel'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the subscription.
|
||||
*
|
||||
* @param bool $pauseNow
|
||||
* @param \DateTimeInterface|string|null $until
|
||||
* @return $this
|
||||
*/
|
||||
public function pause(bool $pauseNow = false, $until = null)
|
||||
{
|
||||
$response = Cashier::api('POST', "subscriptions/{$this->paddle_id}/pause", [
|
||||
'effective_from' => $pauseNow ? 'immediately' : 'next_billing_period',
|
||||
'resume_at' => $until ? Carbon::parse($until)->format(DateTimeInterface::RFC3339) : null,
|
||||
])['data'];
|
||||
|
||||
$pausedAt = $pauseNow ? $response['paused_at'] : $response['scheduled_change']['effective_at'];
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
'paused_at' => Carbon::parse($pausedAt, 'UTC'),
|
||||
])->save();
|
||||
|
||||
$this->syncSubscriptionItems($response['items']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the subscription until a certain date.
|
||||
*
|
||||
* @param \DateTimeInterface|string $until
|
||||
* @return $this
|
||||
*/
|
||||
public function pauseUntil($until)
|
||||
{
|
||||
return $this->pause(false, $until);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the subscription immediately.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function pauseNow()
|
||||
{
|
||||
return $this->pause(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pause the subscription immediately and until a certain date.
|
||||
*
|
||||
* @param \DateTimeInterface|string $until
|
||||
* @return $this
|
||||
*/
|
||||
public function pauseNowUntil($until)
|
||||
{
|
||||
return $this->pause(true, $until);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resume a paused subscription.
|
||||
*
|
||||
* @param \DateTimeInterface|string|null $resumeAt
|
||||
* @return $this
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
public function resume($resumeAt = null)
|
||||
{
|
||||
if ($this->paused()) {
|
||||
$response = Cashier::api('POST', "subscriptions/{$this->paddle_id}/resume", [
|
||||
'effective_from' => $resumeAt
|
||||
? Carbon::parse($resumeAt)->format(DateTimeInterface::RFC3339)
|
||||
: 'immediately',
|
||||
])['data'];
|
||||
} elseif ($this->onPausedGracePeriod()) {
|
||||
$response = Cashier::api('PATCH', "subscriptions/{$this->paddle_id}", [
|
||||
'scheduled_change' => null,
|
||||
])['data'];
|
||||
} else {
|
||||
throw new LogicException('Cannot resume a subscription that is not paused.');
|
||||
}
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
'paused_at' => $response['paused_at'] ? Carbon::parse($response['paused_at'], 'UTC') : null,
|
||||
])->save();
|
||||
|
||||
$this->syncSubscriptionItems($response['items']);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the underlying Paddle subscription information for the model.
|
||||
*
|
||||
* @param array $options
|
||||
* @return array
|
||||
*/
|
||||
public function updatePaddleSubscription(array $options)
|
||||
{
|
||||
return Cashier::api('PATCH', "subscriptions/{$this->paddle_id}", $options)['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the subscription at the end of the current billing period.
|
||||
*
|
||||
* @param bool $cancelNow
|
||||
* @return $this
|
||||
*/
|
||||
public function cancel(bool $cancelNow = false)
|
||||
{
|
||||
$response = Cashier::api('POST', "subscriptions/{$this->paddle_id}/cancel", [
|
||||
'effective_from' => $cancelNow ? 'immediately' : 'next_billing_period',
|
||||
])['data'];
|
||||
|
||||
$endsAt = $cancelNow ? $response['canceled_at'] : $response['scheduled_change']['effective_at'];
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
'ends_at' => Carbon::parse($endsAt, 'UTC'),
|
||||
'trial_ends_at' => $cancelNow ? null : $this->trial_ends_at,
|
||||
])->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the subscription immediately.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cancelNow()
|
||||
{
|
||||
return $this->cancel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the subscription from being canceled at the end of the current billing period.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function stopCancelation()
|
||||
{
|
||||
$response = $this->updatePaddleSubscription(['scheduled_change' => null]);
|
||||
|
||||
$this->forceFill([
|
||||
'status' => $response['status'],
|
||||
'ends_at' => null,
|
||||
])->save();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last payment for the subscription.
|
||||
*
|
||||
* @return \Laravel\Paddle\Payment|null
|
||||
*/
|
||||
public function lastPayment()
|
||||
{
|
||||
if ($transaction = $this->transactions()->orderByDesc('billed_at')->first()) {
|
||||
return new Payment($transaction->total, $transaction->currency, $transaction->billed_at);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next payment for the subscription.
|
||||
*
|
||||
* @return \Laravel\Paddle\Payment|null
|
||||
*/
|
||||
public function nextPayment()
|
||||
{
|
||||
if ($transaction = $this->asPaddleSubscription('next_transaction')['next_transaction'] ?? null) {
|
||||
return new Payment(
|
||||
$transaction['details']['totals']['grand_total'],
|
||||
$transaction['details']['totals']['currency_code'],
|
||||
Carbon::parse($transaction['billing_period']['starts_at'], 'UTC'),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the subscription as a Paddle subscription response.
|
||||
*
|
||||
* @param string|null $include
|
||||
* @return array
|
||||
*/
|
||||
public function asPaddleSubscription(?string $include = null)
|
||||
{
|
||||
$include = $include ? ['include' => $include] : [];
|
||||
|
||||
return Cashier::api('GET', "subscriptions/{$this->paddle_id}", $include)['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically set the proration behavior when invoicing immediately.
|
||||
*
|
||||
* @param string $method
|
||||
* @return void
|
||||
*
|
||||
* @throws \LogicException
|
||||
*/
|
||||
protected function setProrateAndInvoice($method): void
|
||||
{
|
||||
if ($this->prorationBehavior === 'do_not_bill') {
|
||||
throw new LogicException("You cannot combine {$method} and doNotBill.");
|
||||
}
|
||||
|
||||
if ($this->prorationBehavior === 'prorated_next_billing_period') {
|
||||
$this->prorateImmediately();
|
||||
} elseif ($this->prorationBehavior === 'full_next_billing_period') {
|
||||
$this->immediatelyWithoutProrate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the subscription items with the latest data from Paddle.
|
||||
*
|
||||
* @param array $items
|
||||
* @return void
|
||||
*/
|
||||
protected function syncSubscriptionItems(array $items)
|
||||
{
|
||||
$prices = [];
|
||||
|
||||
foreach ($items as $item) {
|
||||
$prices[] = $item['price']['id'];
|
||||
|
||||
$this->items()->updateOrCreate([
|
||||
'price_id' => $item['price']['id'],
|
||||
], [
|
||||
'product_id' => $item['price']['product_id'],
|
||||
'status' => $item['status'],
|
||||
'quantity' => $item['quantity'] ?? 1,
|
||||
]);
|
||||
}
|
||||
|
||||
// Delete items that aren't attached to the subscription anymore...
|
||||
$this->items()->whereNotIn('price_id', $prices)->delete();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user