Ad

Use Select Element Without Dropdown

I currently have a pricing page on my website that uses the <select> and <option> html elements to let users select a pricing plan.

This is the code for the plan select dropdown:

<select name="plan" class="form-control" id="subscription-plan">
    @foreach ($plans as $key => $plan)
        <option value="{{ $key }}">{{ $plan }}</option>
    @endforeach
</select>

And here is what the form looks like:

enter image description here

I would like to add 2 cards containing the plan info that can be selected by the user and act the same way as the dropdown, except without it being a dropdown. The id="subscription-plan" is important because it sends the corresponding plan the user selects to the Stripe API, which bills the user's credit card for the amount set in Stripe. Does anyone know how I could achieve this?

I would like to do something like this (but without the dropdown styling):

<select name="plan" class="form-control" id="subscription-plan">
                @foreach ($plans as $key => $plan)
                    <option value="{{ $key }}">
                        <div class="card mb-5 mb-lg-0">
                            <div class="card-body">
                                <h5 class="card-title text-muted text-uppercase text-center">Monthly</h5>
                                <h6 class="card-price text-center">$8.44<span class="period">/month</span></h6>
                            </div>
                        </div>
                    </option>
                @endforeach
            </select>

UPDATE: I have tried this:

@foreach ($plans as $key => $plan)
        <div class="col-6">
            <label>
                <input type="radio" name="plan" value="{{ $key }}" class="card-input-element" id="subscription-plan">

                <div class="panel panel-default card-input">
                    <div class="panel-heading">{{ $plan }}</div>
                    <div class="panel-body">2 Memorials</div>
                </div>
            </label>
        </div>
    @endforeach

This code works and gives me the desired effect of having selectable cards, but the Stripe API now only receives the "monthly" plan even if the user submits their credit card details after selecting the "yearly" plan. This is because now both inputs have id="subscription-plan". enter image description here

Stripe JavaScript:

        const stripe = Stripe('{{ env('STRIPE_KEY') }}');
        const elements = stripe.elements();
        const cardElement = elements.create('card');
        cardElement.mount('#card-element');
        const cardHolderName = document.getElementById('card-holder-name');
        const cardButton = document.getElementById('card-button');
        const clientSecret = cardButton.dataset.secret;
        const plan = document.getElementById('subscription-plan').value;

        cardButton.addEventListener('click', async (e) => {
            const { setupIntent, error } = await stripe.handleCardSetup(
                clientSecret, cardElement, {
                    payment_method_data: {
                        billing_details: { name: cardHolderName.value }
                    }
                }
            );
            if (error) {
                // Display "error.message" to the user...
            } else {
                // The card has been verified successfully...
                console.log('handling success', setupIntent.payment_method);

                axios.post('subscribe',{
                    payment_method: setupIntent.payment_method,
                    plan : plan
                }).then((data)=>{
                    location.replace(data.data.success_url)
                });
            }
        });

UPDATE 2: I have replaced const plan = document.getElementById('subscription-plan').value; with var plan = document.querySelector('input[name="plan"]:checked').value;. Now I get Uncaught TypeError: Cannot read property 'value' of null in console when I try to submit.

Ad

Answer

I solved the JavaScript side of this issue on my own. What I did was change var plan = document.querySelector('input[name="plan"]:checked').value; into just var plan;. Then, I added: $('input:radio').on('click', function(e) { plan = this.value; });.

Now, the form properly submits the correct plan based on the option the user selects to the Stripe API. This was a surprisingly difficult task for me to get this form to work without the use of the <select> and <option> elements. Here is the finished working code:

HTML

<div class="col-6">
        <label>
            <input type="radio" name="plan" value="monthly" class="card-input-element" checked="checked">

            <div class="panel panel-default card-input">
                <div class="panel-heading">Monthly</div>
            </div>
        </label>
    </div>
    <div class="col-6">
        <label>
            <input type="radio" name="plan" value="yearly" class="card-input-element">

            <div class="panel panel-default card-input">
                <div class="panel-heading">Yearly</div>
            </div>
        </label>
    </div>

JavaScript

<script src="{{ asset('js/jquery-3.4.1.min.js') }}"></script>
<script>
    window.addEventListener('load', function() {
        const stripe = Stripe('{{ env('STRIPE_KEY') }}');
        const elements = stripe.elements();
        const cardElement = elements.create('card');
        cardElement.mount('#card-element');
        const cardHolderName = document.getElementById('card-holder-name');
        const cardButton = document.getElementById('card-button');
        const clientSecret = cardButton.dataset.secret;
        var plan;
        $('input:radio').on('click', function(e) {
            plan = this.value;
        });

        cardButton.addEventListener('click', async (e) => {
            const { setupIntent, error } = await stripe.handleCardSetup(
                clientSecret, cardElement, {
                    payment_method_data: {
                        billing_details: { name: cardHolderName.value }
                    }
                }
            );
            if (error) {
                // Display "error.message" to the user...
            } else {
                // The card has been verified successfully...
                console.log('handling success', setupIntent.payment_method);

                axios.post('subscribe',{
                    payment_method: setupIntent.payment_method,
                    plan : plan
                }).then((data)=>{
                    location.replace(data.data.success_url)
                });
            }
        });
    });
</script>

More experienced programmers may have known how to achive this from the start, but I guess we all have to learn somehow. Thank you all for your help.

Ad
source: stackoverflow.com
Ad