#meags-teamvash_code

1 messages ยท Page 1 of 1 (latest)

hearty rockBOT
#

๐Ÿ‘‹ 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/1370110931304190042

๐Ÿ“ Have more to share? Add more details, code, screenshots, videos, etc. below.

shadow spear
#

๐Ÿ‘‹

#

Taking a look

hot sun
shadow spear
#

Can you show me your full code snippet here

hot sun
#
"use client";

import React from "react";
import {
    PaymentElement,
    useCheckout,
} from "@stripe/react-stripe-js";
import type {StripeCheckoutSavedPaymentMethod} from "@stripe/stripe-js";
import {CiCreditCard1} from "react-icons/ci";
import BillingAddressForm
    from "@/app/(site)/product/[productName]/[productId]/checkout/BillingAddressForm.tsx";

interface Props {
    savedPaymentMethods: StripeCheckoutSavedPaymentMethod[];
    selectedPM: string | null;
    onSelectPM: (id: string | null) => void;
    isSubscription: boolean;
    setBillingCountry: React.Dispatch<React.SetStateAction<string>>;
}
#
export default function PaymentDetails({
                                           savedPaymentMethods,
                                           selectedPM,
                                           onSelectPM,
                                           isSubscription,
                                           setBillingCountry,
                                       }: Props) {
    const checkout = useCheckout();
    const saved = savedPaymentMethods[0] ?? null;

    const needsBilling =
        checkout?.tax?.status === "requires_billing_address";


    const renderOption = (
        id: string | null,
        logo: React.ReactNode,
        label: React.ReactNode
    ) => {
        const isSelected = selectedPM === id;
        return (
            <button
                key={id ?? "new"}
                type="button"
                onClick={() => onSelectPM(id)}
                className={`flex w-full items-center gap-2 rounded-lg border px-3 py-2 transition
          ${isSelected ? "border-blue-500 bg-blue-50 ring-2 ring-blue-200"
                    : "border-gray-300 bg-white hover:border-gray-400"}`}
            >

                <span
                    className={`h-4 w-4 rounded-full border-2
            ${isSelected ? "border-blue-600 bg-blue-600"
                        : "border-gray-400 bg-white"}`}
                />

                <span className="flex items-center gap-2">
          {logo}
                    <span className="font-medium text-gray-800 truncate">{label}</span>
        </span>
            </button>
        );
    };
#
return (
        <div className="space-y-2">
            {saved &&
                renderOption(
                    saved.id,
                    <img
                        src={`/logos/${saved.card?.brand}.svg`}
                        alt={saved.card?.brand}
                        className="h-6 w-8 object-contain"
                    />,
                    <>**** {saved.card?.last4}</>
                )}

            {renderOption(
                null,
                <CiCreditCard1 className="h-6 w-6 text-gray-500" />,
                "Use a new payment method"
            )}

            {needsBilling && (
                <BillingAddressForm setBillingCountry={setBillingCountry}/>
            )}


            {selectedPM === null && <PaymentElement />}
        </div>
    );
}
#
import React, { useMemo } from "react";
import { AddressElement, useCheckout } from "@stripe/react-stripe-js";
import type { StripeShippingAddressElementOptions } from "@stripe/stripe-js";

export default function BillingAddressForm({
                                               setBillingCountry,
                                           }: {
    setBillingCountry: (c: string) => void;
}) {
    const checkout = useCheckout();

    //tried this earlier
    const defaultValues = useMemo<StripeShippingAddressElementOptions["defaultValues"]>(() => {
        const b = checkout.billingAddress;
        if (!b) return undefined;

        return {
            name: b.name ?? null,
            address: {
                line1:       b.address.line1       ?? null,
                line2:       b.address.line2       ?? null,
                city:        b.address.city        ?? null,
                state:       b.address.state       ?? null,
                postal_code: b.address.postal_code ?? null,
                country:     b.address.country,    // must be present
            },
        };
    }, [checkout.billingAddress]);


    const needsBilling = checkout.tax?.status === "requires_billing_address";

    if (!needsBilling) return null;

    return (
        <AddressElement
            options={{
                // Pre-fill any existing data
                defaultValues: {
                    name: "Test"
                },
                mode: 'billing',
            }}
            onChange={(event) => {
                setBillingCountry(event.value?.address?.country || "");
            }}
        />
    );
}
#

This is the two relevant components

shadow spear
#

Thanks, and if you remove defaultValues does it render as expected?

hot sun
#

It's just the defaultValues part throwing the error

#

I'm curious if you can re-create the error on your end with a minimal example, just passing in that defaultValue

shadow spear
#

Yeah I will test that but I'm also wondering if we don't support passing defaultValues here in this flow as it potentially should be prefilled based on the Customer object. Let me check on that.

hot sun
#

then the gst shows up

shadow spear
#

Can you share that Checkout Session? That looks to be a different once from before as that is a different saved card.

hot sun
shadow spear
#

Oh did you add the 4242 card separately?

#

Like it looks like you have the 0341 card attached to that Customer

hot sun
#

Hmm let me try the flow again one second

#

cs_test_a1lRSKPyLwySlpumnuRK54MOlWcrMuXuPKefpeVPtENBp8mZ8TNNE4DPqO8

the saved payment method shows up as "pm_1RMYzSGhbFSrlrBCTEHFD65l" the 4242 as it only shows one card.

shadow spear
#

Okay that PaymentMethod is attached to a different Customer than the one related to the Checkout Session you shared above

#

So I think you are mixing up IDs

#

But that helps

#

Oh wait wait

#

My bad

#

I was looking at the default

hot sun
shadow spear
#

This Customer has several PaymentMethods

#

Okay okay, one moment let me look a bit more at this

hot sun
shadow spear
#

Okay so first, coming back around to your initial question -- using defaultValues with Address Element isn't currently supported in this flow. I can flag feedback about that but the concept right now is that by passing the Customer through with a saved PaymentMethod should prefill the necessary address pieces from Payment Element.

I do understand why you would want to show that full address in Address Element so I'll file feedback on that front.

#

Second, to be clear, you are saying that when you render Payment Element here you are not seeing the Tax calculated initially, correct?

hot sun
hot sun
shadow spear
#

Ah okay you are just concerned with the Tax amount being assessed.

#

Got it

#

Hmm yeah I would expect the billing details of the attached PaymentMethod to be used for that

#

So I'm surprised, let me investigate that a bit more

hot sun
#

A customer who has purchased before and and chooses a saved payment method should see the tax calculated on initial load and not have to put their address in again

#

Well I'm not sure if it's known what payment method is chosen on initial load as the payment method is sent in with confirm()

           if (isUsingSavedCard) {
                opts.paymentMethod = selectedPM!;
            } else if (amount > 0) {
                opts.savePaymentMethod = saveNewCard;
            }

            const result = await confirm(opts);
#

I just render the payment method as an option they can select and then on confirm it passes it in

shadow spear
hot sun
#

Okay perhaps something needs to be set when creating the checkout session?

#

Well I have ```ts
automatic_tax: {
enabled: true,
},
billing_address_collection: 'required',

shadow spear
#

What happens if you set billing_address_collection to auto

#

Yes that's what I was going to suggest testing

hot sun
#

Didn't work, cs_test_a1JfgMhjf9MatwiWeX5dHuCOw0XcRU3TQiLopfgBPfeXSiL18lzz8aTzNmz

shadow spear
hot sun
#

While I'm testing it, I noticed this, perhaps I have to set that on load

updateBillingAddress: (
    billingAddress: StripeCheckoutContact | null
  ) => Promise<StripeCheckoutUpdateAddressResult>;
#

I notice the saved payment card has the billing address and the card there
But this is only linked on confirm() so perhaps theres a way tos et this on load too, I'm checking

shadow spear
#

Out of curiosity, have you completed one of these Sessions?

#

I'm doing some testing on my end as well and noticing that this might be a UI bug but tax actually gets assessed.

#

Going to test again to make sure I'm testing it correctly, but I think that is what just occurred.

hot sun
#

Oh yes I forgot, this is what shows up when I click submit

shadow spear
#

Oh that's no good

#

Does it calculate tax at that point?

hot sun
#

But it doesn't update the UI with the total or tax parts. I'll chekc what the checkout session object shows though

shadow spear
#

It does let you submit on the second attempt?

hot sun
shadow spear
#

And it just keeps showing that error

hot sun
#

Ah I fixed it!

shadow spear
#

??!!

hot sun
#

I added this

    useEffect(() => {
        if (!saved || !checkout) return;          // nothing to do

        const src = saved.billingDetails?.address;
        if (!src || !src.country) return;         // address incomplete โ†’ skip

        const normalized: StripeCheckoutContact = {
            name: saved.billingDetails?.name ?? undefined,
            address: {
                country:     src.country,             // now definitely a string
                line1:       src.line1       ?? undefined,
                line2:       src.line2       ?? undefined,
                city:        src.city        ?? undefined,
                state:       src.state       ?? undefined,
                postal_code: src.postal_code ?? undefined,
            },
        };

        checkout.updateBillingAddress(normalized);
    }, [saved, checkout]);
#

Calling updatebilling address on load based on the saved payment card

#

Not sure if that's the ideal way but the tax rendered right away

shadow spear
#

Ahhh okay I thought you had already been doing that.

#

That said, I agree, that shouldn't be needed!

#

It should just prefill it based on the PaymentMethod

#

So I'll submit feedback on that.

#

But I'm glad there is a workaround for now

hot sun
shadow spear
#

Nah it should still just assess the tax based on the billing details of the PaymentMethod imo

hot sun
#

Great if you could submit that feedback, that would be great. thanks for your help debugging! ๐Ÿ™‚