Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

arcanaer's avatar

Livewire lost data when emit event

i'm having a problem when i emit event from javascript to Livewire.

In my mount function i'm storing plan public propertie, when i emit the Livewire.emit('newSubscription', setupIntent.payment_method); event from javascript to my component and i trying to get the plan propertie the value is the model but with empty properties.

Pay.php

class Pay extends Component
{
public $paymentMethod;
public $slug;
public $publicKey;
public $interval;
public $plan;
private $p;
public $payWithNewCard = false;

protected $listeners = ['newSubscription'];

public function mount($slug, $interval)
{
    $this->publicKey = config('cashier.key');
    $this->slug = $slug;
    $this->interval = $interval;

    $user = auth()->user();

    //Check if the user has a subscription
    if ( auth()->user()->subscribed('default') ) {
        return redirect()->route('billing.subscription');
    }

    if ( $interval != 'month' && $interval != 'year' ) {
        return redirect()->route('billing.premium');
    }

    //Get plan
    $this->plan = Product::select('products.slug', 
    'products.trial_period_days', 'prices.currency', 'prices.interval', 
    'products.stripe_product_id as product', 
    'products.name', 'prices.amount', 'products.trial_period_days', 
    'prices.stripe_price_id', 'prices.id as price_id')
    ->where('products.active', '=', '1')
    ->where('prices.active', '=', '1')
    ->where('products.slug', '=', $this->slug)
    ->where('prices.interval', '=', $this->interval)
    ->join('prices', 'products.id', '=', 'prices.product_id')->first();

    dump($this->plan);
    //If plan doesn't exists, redirect to premium
    if ( !$this->plan ) {
        return redirect()->route('billing.premium');
    }
}

public function render()
{
    return view('livewire.billing.pay', [
        'intent' => auth()->user()->createSetupIntent()
    ]);
}

public function newSubscription($paymentMethod)
{
    try {
        //TODO: Validate only trial in first time
        $user = auth()->user();

            //If user has payment method
        if ( $user->hasPaymentMethod() ) {
            $user->addPaymentMethod($paymentMethod);
        } else {
            $user->updateDefaultPaymentMethod($paymentMethod);
        }
        dd($this->plan);
        if ( $this->plan->trial_period_days ) {
            $result = $user
            ->newSubscription('default', $this->plan->price_id)
            ->trialDays($this->plan->trial_period_days)
            ->create(($paymentMethod) ? $paymentMethod : '');
        } else {
            $result = $user
            ->newSubscription('default', $this->plan->price_id)
            ->create(($paymentMethod) ? $paymentMethod : '');
        }
            
            return redirect()->route('billing.success');
        
    } catch ( IncompletePayment $exception ) {
        return redirect()->route(
            'cashier.payment',
            [$exception->payment->id, 'redirect' => route('billing.index')]
        );
    }
}

}

Pay.blade.php

<div class="container" x-cloak x-data="pay()" x-init="mount()">
<div class="mb-4 plan-selected mx-auto px-8 py-4 text-center bg-white rounded-lg shadow">
    <div class="flex justify-between">
        <div class="flex items-center">
            <h3 class="text-2xl font-bold font-heading">{{ $plan->name }}</h3>
        </div>
        <div>
            <span class="flex items-center justify-center text-2xl font-bold text-black font-heading">${{ $plan->amount }}<span class="ml-1 text-xs">{{ $plan->currency }}</span><span class="ml-1 text-xs">/ {{ $plan->interval }}</span></span>
            <div class="text-right flex flex-col">
                <a class="text-xs text-right ml-auto text-blue-500" href="{{ route('billing.premium') }}">Seleccionar otro plan</a>
                @if ( auth()->user()->hasDefaultPaymentMethod() )
                    <span class="text-xs text-right mt-4 ml-auto text-blue-500 cursor-pointer" x-on:click="payWithNewCard = !payWithNewCard">Elegir otro método de pago</span>
                @endif
            </div>
        </div>
        
    </div>
</div>
<div class="mb-4 plan-selected mx-auto px-8 py-4 text-center bg-white rounded-lg shadow">
    @if ( auth()->user()->hasDefaultPaymentMethod() )
        <div x-show="!payWithNewCard" x-transition>
            <!-- If the user has default payment method -->
            <x-button wire:loading.attr="disabled" wire:target="newSubscription" wire:click="newSubscription('{{ $plan->stripe_price_id }}')">
                <x-loading wire:target="newSubscription"></x-loading>
                Pagar con tarjeta terminación {{ auth()->user()->pm_last_four }}
            </x-button>    
        </div>
        <div x-show="payWithNewCard" x-transition>
            <!-- If the user doesn't has payment method, show form to add payment method -->
            <div class="">
                <div class="relative">
                    <form action="" id="card-form">
                        <div class="card-body">
                            <div class="flex space-x-8">
                                <div class="flex-1">
                                    <div>
                                        {{ $paymentMethod }}
                                        <input id="card-holder-name" placeholder="Nombre del títular de la tarjeta" type="text" class="form-control">
                                    </div>
                                    <div >
                                        <!-- Stripe Elements Placeholder -->
                                        <div class="form-control" id="card-element"></div>
                                        <span id="card-error"></span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </form>
                    <div class="card-footer flex flex-col md:flex-row justify-end">
                        <x-button id="card-button" x-on:click="createPaymentMethod()"  data-secret="{{ $intent->client_secret }}">
                            <x-loading></x-loading>
                            Pagar
                        </x-button>
                    </div>
                    <div class="bg-red-700 mt-2 rounded text-white" id="cardErrors">
                    
                    </div>
                </div>
        </div>
            

    @else
        <!-- If the user doesn't has payment method, show form to add payment method -->
        @livewire('billing.payment-method-create', ['buttonText' => 'Pagar', 'name' => $plan->name, 'priceId' => $plan->stripe_price_id])
    @endif    
</div>
@push('scripts')
    <script>
        function pay()
        {
            return {
                payWithNewCard:false,
                stripe:null,
                elements:null,
                cardElement:null,
                cardHolderName:null,
                cardButton:null,
                clientSecret:null,
                mount()
                {
                    this.stripe = Stripe("{{ $publicKey }}");
                    this.elements = this.stripe.elements();
                    this.cardElement = this.elements.create('card');
                    this.cardElement.mount('#card-element');

                    this.cardHolderName = document.getElementById('card-holder-name');
                    this.cardButton = document.getElementById('card-button');
                    this.clientSecret = this.cardButton.dataset.secret;
                },
                async createPaymentMethod ()
                {
                    //TODO: Verify that card doesnt exists, if exists get the id and pass
                    this.payWithNewCard = true;
                    const { setupIntent, error } = await this.stripe.confirmCardSetup(
                        this.clientSecret, {
                            payment_method: {
                                card: this.cardElement,
                                billing_details: { name: this.cardHolderName.value }
                            }
                        }
                    );

                    if (error) {
                        document.getElementById('cardErrors').textContent = error.message;
                    } else {
                        Livewire.emit('newSubscription', setupIntent.payment_method);
                    }
                }
            }
        }
    </script>
@endpush

Image with dump when component is mounted

Image with dump after emit NewSubscriptionEvent

0 likes
3 replies
Snapey's avatar

this is to be expected

livewire will attempt to rehydrate the model (s) that are stored in public attributes but it knows nothing about joined data

hold plain models in public property or cast it to an array

1 like
arcanaer's avatar

@Snapey Thanks for reply. What do you refer with cast to array? Iterate plan with foreach and create a new array from Plan variable?

Snapey's avatar

@arcanaer add toArray() to your query. You would though need to use array syntax in blade

alternatively load prices as a relation not as a join

Please or to participate in this conversation.