diff --git a/src/Concerns/HandlesPaymentFailures.php b/src/Concerns/HandlesPaymentFailures.php new file mode 100644 index 00000000..ec9fb0f6 --- /dev/null +++ b/src/Concerns/HandlesPaymentFailures.php @@ -0,0 +1,59 @@ +hasIncompletePayment()) { + try { + $subscription->latestPayment()->validate(); + } catch (IncompletePayment $e) { + if ($e->payment->requiresConfirmation()) { + try { + if ($paymentMethod) { + $paymentIntent = $e->payment->confirm([ + 'expand' => ['invoice.subscription'], + 'payment_method' => $paymentMethod instanceof StripePaymentMethod + ? $paymentMethod->id + : $paymentMethod, + ]); + } else { + $paymentIntent = $e->payment->confirm(['expand' => ['invoice.subscription']]); + } + } catch (StripeCardException) { + $paymentIntent = $e->payment->asStripePaymentIntent(['invoice.subscription']); + } + + $subscription->fill([ + 'stripe_status' => $paymentIntent->invoice->subscription->status, + ])->save(); + + if ($subscription->hasIncompletePayment()) { + (new Payment($paymentIntent))->validate(); + } + } else { + throw $e; + } + } + } + } +} diff --git a/src/Concerns/InteractsWithPaymentBehavior.php b/src/Concerns/InteractsWithPaymentBehavior.php index 486bf54e..e7f38161 100644 --- a/src/Concerns/InteractsWithPaymentBehavior.php +++ b/src/Concerns/InteractsWithPaymentBehavior.php @@ -11,7 +11,19 @@ trait InteractsWithPaymentBehavior * * @var string */ - protected $paymentBehavior = StripeSubscription::PAYMENT_BEHAVIOR_ALLOW_INCOMPLETE; + protected $paymentBehavior = StripeSubscription::PAYMENT_BEHAVIOR_DEFAULT_INCOMPLETE; + + /** + * Set any new subscription as incomplete when created. + * + * @return $this + */ + public function defaultIncomplete() + { + $this->paymentBehavior = StripeSubscription::PAYMENT_BEHAVIOR_DEFAULT_INCOMPLETE; + + return $this; + } /** * Allow subscription changes even if payment fails. diff --git a/src/Subscription.php b/src/Subscription.php index 4b81fae6..3f073ed0 100644 --- a/src/Subscription.php +++ b/src/Subscription.php @@ -10,6 +10,7 @@ use Illuminate\Support\Collection; use InvalidArgumentException; use Laravel\Cashier\Concerns\AllowsCoupons; +use Laravel\Cashier\Concerns\HandlesPaymentFailures; use Laravel\Cashier\Concerns\InteractsWithPaymentBehavior; use Laravel\Cashier\Concerns\Prorates; use Laravel\Cashier\Database\Factories\SubscriptionFactory; @@ -24,6 +25,7 @@ class Subscription extends Model { use AllowsCoupons; + use HandlesPaymentFailures; use HasFactory; use InteractsWithPaymentBehavior; use Prorates; @@ -526,11 +528,7 @@ public function updateQuantity($quantity, $price = null) 'quantity' => $stripeSubscription->quantity, ])->save(); - if ($this->hasIncompletePayment()) { - (new Payment( - $stripeSubscription->latest_invoice->payment_intent - ))->validate(); - } + $this->handlePaymentFailure($this); return $this; } @@ -678,6 +676,7 @@ public function extendTrial(CarbonInterface $date) * @param array $options * @return $this * + * @throws \Laravel\Cashier\Exceptions\IncompletePayment * @throws \Laravel\Cashier\Exceptions\SubscriptionUpdateFailure */ public function swap($prices, array $options = []) @@ -722,11 +721,7 @@ public function swap($prices, array $options = []) $this->unsetRelation('items'); - if ($this->hasIncompletePayment()) { - (new Payment( - $stripeSubscription->latest_invoice->payment_intent - ))->validate(); - } + $this->handlePaymentFailure($this); return $this; } @@ -876,13 +871,21 @@ public function addPrice($price, $quantity = 1, array $options = []) $this->unsetRelation('items'); + $stripeSubscription = $this->asStripeSubscription(); + if ($this->hasSinglePrice()) { $this->fill([ 'stripe_price' => null, 'quantity' => null, - ])->save(); + ]); } + $this->fill([ + 'stripe_status' => $stripeSubscription->status, + ])->save(); + + $this->handlePaymentFailure($this); + return $this; } diff --git a/src/SubscriptionBuilder.php b/src/SubscriptionBuilder.php index 4f1e39f3..6e931ae7 100644 --- a/src/SubscriptionBuilder.php +++ b/src/SubscriptionBuilder.php @@ -9,6 +9,7 @@ use Illuminate\Support\Collection; use InvalidArgumentException; use Laravel\Cashier\Concerns\AllowsCoupons; +use Laravel\Cashier\Concerns\HandlesPaymentFailures; use Laravel\Cashier\Concerns\HandlesTaxes; use Laravel\Cashier\Concerns\InteractsWithPaymentBehavior; use Laravel\Cashier\Concerns\Prorates; @@ -17,6 +18,7 @@ class SubscriptionBuilder { use AllowsCoupons; + use HandlesPaymentFailures; use HandlesTaxes; use InteractsWithPaymentBehavior; use Prorates; @@ -258,11 +260,7 @@ public function create($paymentMethod = null, array $customerOptions = [], array $subscription = $this->createSubscription($stripeSubscription); - if ($subscription->hasIncompletePayment()) { - (new Payment( - $stripeSubscription->latest_invoice->payment_intent - ))->validate(); - } + $this->handlePaymentFailure($subscription, $paymentMethod); return $subscription; } diff --git a/src/SubscriptionItem.php b/src/SubscriptionItem.php index 594cb32e..c9d2baf3 100644 --- a/src/SubscriptionItem.php +++ b/src/SubscriptionItem.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; +use Laravel\Cashier\Concerns\HandlesPaymentFailures; use Laravel\Cashier\Concerns\InteractsWithPaymentBehavior; use Laravel\Cashier\Concerns\Prorates; use Laravel\Cashier\Database\Factories\SubscriptionItemFactory; @@ -15,6 +16,7 @@ */ class SubscriptionItem extends Model { + use HandlesPaymentFailures; use HasFactory; use InteractsWithPaymentBehavior; use Prorates; @@ -115,18 +117,19 @@ public function updateQuantity($quantity) 'quantity' => $stripeSubscriptionItem->quantity, ])->save(); - if ($this->subscription->hasSinglePrice()) { - $stripeSubscription = $this->subscription->asStripeSubscription(); + $stripeSubscription = $this->subscription->asStripeSubscription(); + if ($this->subscription->hasSinglePrice()) { $this->subscription->fill([ - 'stripe_status' => $stripeSubscription->status, 'quantity' => $stripeSubscriptionItem->quantity, - ])->save(); + ]); } - if ($this->subscription->hasIncompletePayment()) { - optional($this->subscription->latestPayment())->validate(); - } + $this->subscription->fill([ + 'stripe_status' => $stripeSubscription->status, + ])->save(); + + $this->handlePaymentFailure($this->subscription); return $this; } @@ -162,16 +165,20 @@ public function swap($price, array $options = []) 'quantity' => $stripeSubscriptionItem->quantity, ])->save(); + $stripeSubscription = $this->subscription->asStripeSubscription(); + if ($this->subscription->hasSinglePrice()) { $this->subscription->fill([ 'stripe_price' => $price, 'quantity' => $stripeSubscriptionItem->quantity, - ])->save(); + ]); } - if ($this->subscription->hasIncompletePayment()) { - optional($this->subscription->latestPayment())->validate(); - } + $this->subscription->fill([ + 'stripe_status' => $stripeSubscription->status, + ])->save(); + + $this->handlePaymentFailure($this->subscription); return $this; }