#usman-khan_unexpected

1 messages · Page 1 of 1 (latest)

magic surgeBOT
#

👋 Welcome to your new thread!

⏲️ We'll be here soon! Typically we respond in a few minutes, but sometimes we might take a bit longer if the server is busy or if you have a particularly tricky question.

⏱️ We close idle threads, which makes them read-only. Once a thread is closed it won't be reopened, but you can always start a new thread if you have another question.

🔗 This thread will always be available, even after it's closed. You can find it again using Discord's search, or you can save this link: https://discord.com/channels/841573134531821608/1366764540519514133

📝 Have more to share? Add more details, code, screenshots, videos, etc. below.

knotty sedge
#

Hey there, how did you make the change to the other plan? It is possible to do this both with and without ending the trial and starting the paid cycle

#

So it depends what was done

rose grove
#

I use swap and internally it generates payload which contains the end time correctly. and it was working for about 2 years. and now it started to charge users during trials. when user switch plan.

#

swaping by giving it new price. which chnage current subscription to new. at a time user has one subscription only.

knotty sedge
#

what are swap and onTrial? Can you share a specific subscription ID like sub_123?

#

I expect that you're setting trial_end=now in the update request, forcing the trial to end and billing to start

rose grove
#

Its client and do not have ids from stripe. as i dont have access to that.
and these are provided by a PHP package. Laravel Cashier.

public function swap($prices, array $options = [])
    {
        if (empty($prices = (array) $prices)) {
            throw new InvalidArgumentException('Please provide at least one price when swapping.');
        }

        $this->guardAgainstIncomplete();

        $items = $this->mergeItemsThatShouldBeDeletedDuringSwap(
            $this->parseSwapPrices($prices)
        );

        $stripeSubscription = $this->owner->stripe()->subscriptions->update(
            $this->stripe_id, $this->getSwapOptions($items, $options)
        );

        /** @var \Stripe\SubscriptionItem $firstItem */
        $firstItem = $stripeSubscription->items->first();
        $isSinglePrice = $stripeSubscription->items->count() === 1;

        $this->fill([
            'stripe_status' => $stripeSubscription->status,
            'stripe_price' => $isSinglePrice ? $firstItem->price->id : null,
            'quantity' => $isSinglePrice ? ($firstItem->quantity ?? null) : null,
            'ends_at' => null,
        ])->save();

        $subscriptionItemIds = [];

        foreach ($stripeSubscription->items as $item) {
            $subscriptionItemIds[] = $item->id;

            $this->items()->updateOrCreate([
                'stripe_id' => $item->id,
            ], [
                'stripe_product' => $item->price->product,
                'stripe_price' => $item->price->id,
                'quantity' => $item->quantity ?? null,
            ]);
        }

        // Delete items that aren't attached to the subscription anymore...
        $this->items()->whereNotIn('stripe_id', $subscriptionItemIds)->delete();

        $this->unsetRelation('items');

        $this->handlePaymentFailure($this);

        return $this;
    }
#

and options are these


    /**
     * Get the options array for a swap operation.
     *
     * @param  \Illuminate\Support\Collection  $items
     * @param  array  $options
     * @return array
     */
    protected function getSwapOptions(Collection $items, array $options = [])
    {
        $payload = array_filter([
            'items' => $items->values()->all(),
            'payment_behavior' => $this->paymentBehavior(),
            'promotion_code' => $this->promotionCodeId,
            'proration_behavior' => $this->prorateBehavior(),
            'expand' => ['latest_invoice.payment_intent'],
        ]);

        if ($payload['payment_behavior'] !== StripeSubscription::PAYMENT_BEHAVIOR_PENDING_IF_INCOMPLETE) {
            $payload['cancel_at_period_end'] = false;
        }

        $payload = array_merge($payload, $options);

        if (! is_null($this->billingCycleAnchor)) {
            $payload['billing_cycle_anchor'] = $this->billingCycleAnchor;
        }

        $payload['trial_end'] = $this->onTrial()
                        ? $this->trial_ends_at->getTimestamp()
                        : 'now';

        return $payload;
    }

onTrial returns true or false based on trial. and its giving status of existing subscription. Means if existing sub has trial set end date to that ones end date else set to now.

knotty sedge
#

Its client and do not have ids from stripe. as i dont have access to that.
If you're making requests via code you have access, sharing for example an update request ID would help

#

I can't speak to laravel cashier behaviour, since that's an abstraction they built on top of our SDK, you'd need to speak to the vendor for help with that

#

But I would suggest debugging the behaviour with check the trial and setting trial_end

#

If your request ends up setting trial_end=now what you describe will happen: the trial will end and th customer will be invoiced for the new price provided

rose grove
#

Ill debug and check logs. And if logs are generating correct payload and still its charging then ill share.

magic surgeBOT
rose grove
#

Hey! Quick update —
🔍 After digging deeper, I found that the issue was actually caused by something else in our system — not on Stripe’s end.

It threw me off because everything had been working smoothly for over 2 years, and the relevant code hasn’t changed (confirmed via Git logs).
That’s why I initially suspected a potential update or change on Stripe’s side. 🤷‍♂️

Thanks for being there while I sorted it out! 🙏