#sergx_docs

1 messages · Page 1 of 1 (latest)

candid coveBOT
minor tartanBOT
#

Below are links to other discussions we've had with you in the past week in case you want to review that information. If your question is related to one of these previous discussions, please provide a comprehensive summary of the current state and what you need help with now. We help many users simultaneously, so a summary allows us to resolve your issue as soon as possible.

candid coveBOT
#

👋 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/1238478911415910470

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

novel quest
#

I'm using PHP, this is the code of the server:

public function createSubscription(Request $request)
    {
        $request->validate([
            'business_quantity' => 'required|numeric|min:1',
            'employees_quantity' => 'required|numeric|min:0',
            'period' => 'required|in:month,year',
            'coupon' => 'nullable',
        ]);

        $user = User::find(auth()->id());

        // Seteamos el priceId del plan base
        $planPriceId = $request->period === 'year' ? config('app.opinas_base_year') : config('app.opinas_base_month');
        $employeesPriceId = $request->period === 'year' ? config('app.addon_employees_year') : config('app.addon_employees_month');

        $subscription = $this->stripe->subscriptions->create([
            'customer' => $user->stripe_id,
            'items' => [
                ['price' => $planPriceId, 'quantity' => $request->business_quantity],
                ['price' => $employeesPriceId, 'quantity' => $request->employees_quantity],
            ],
            'proration_behavior' => 'always_invoice',
            'payment_behavior' => 'default_incomplete',
            'coupon' => $request->coupon,
            'payment_settings' => ['save_default_payment_method' => 'on_subscription'],
            'expand' => ['latest_invoice.payment_intent', 'pending_setup_intent'],
        ]);

        if ($subscription->pending_setup_intent !== NULL) {
            return response()->json([
                'type' => 'setup',
                'clientSecretStripe' => $subscription->pending_setup_intent->client_secret
            ]);
        } else {
            return response()->json([
                'type' => 'payment',
                'clientSecretStripe' => $subscription->latest_invoice->payment_intent->client_secret
            ]);
        }
    }
void geyser
#

Hey there!

#

I got an error
What is the error you're hitting?

novel quest
#

This is the code of javascript

const stripe = Stripe("{{ config('app.stripe_key') }}");
            const options = {
                mode: 'subscription',
                amount: 2420,
                currency: 'eur',
            }
            const elements = stripe.elements(options);
            const paymentElement = elements.create('payment');
            paymentElement.mount('#payment-element');

const subscriptionEndpoint = `/checkout/subscription/create`;
    const subscriptionResponse = await fetch(subscriptionEndpoint, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'Accept': 'application/json',
            'X-CSRF-TOKEN': document.querySelector('[name="_token"]').value
        },
        body: JSON.stringify({
            business_quantity: businessQuantityValue.value,
            employees_quantity: employeesQuantityValue.value,
            period: periodValue.value,
            coupon: coupon
        })
    });
    const { type, clientSecretStripe } = await subscriptionResponse.json();
    console.log(type, clientSecretStripe)
    const confirmIntent = type === "setup" ? stripe.confirmSetup : stripe.confirmPayment;

    stopPreventingPageUnload();

    // Confirmamos el intento utilizando los datos del formulario
    const {error} = await confirmIntent({
        elements,
        clientSecretStripe,
        confirmParams: {
            return_url: 'https://example.com/order/123/complete',
        },
    });

    if (error) {
        errorMessage.textContent = error.message;
        updateButtonLoadingState(false);
    }

#

Getting this error

#

the console log of type and clientSecretStripe is this:

"payment" "pi_3PEtEVKYINwhn6n40CDk2y7t_secret_nBYGzGj36xkmVdIXccsPZVufI"

void geyser
#

I think this is just a parameter naming issue

#

you have:

const {error} = await confirmIntent({
        elements,
        clientSecretStripe,
        confirmParams: {
            return_url: 'https://example.com/order/123/complete',
        },
    });
#

but we require clientSecret

#

try:

const {error} = await confirmIntent({
        elements,
        clientSecret: clientSecretStripe,
        confirmParams: {
            return_url: 'https://example.com/order/123/complete',
        },
    });
novel quest
#

I send you a screenshot of the new error

void geyser
#

Yep, you need to call elements.submit() first

novel quest
#

That works thank you very much! I have a doubt about this

#
const stripe = Stripe("{{ config('app.stripe_key') }}");
            const options = {
                mode: 'subscription',
                amount: 2420,
                currency: 'eur',
            }
#

options would be neccesary to be passed to elements?

#

The problem is that the checkout page I'm building is really dynamic, so you can select multiple products (that will added to the subscription)

void geyser
#

Can you clarify? I don't understand the question.

novel quest
#

So amount: 2420 would be not accurate

#

or it's just a reference for Stripe?

void geyser
#

It can change which payment methods are presented, based on currency or minimum amounts etc

#

The amount does not need to match the final confirmation, but updating can help with conversion

novel quest
#

perfect thank you very very much, if you can let this chat open please for a while, I will do some testing, I've been dealing this morning with some problems I'd like to check

#

alright here's something

#

I've used 4000000000000341 to test payment failures

#

When the card is declined I see that error in the frontend.

The problem is an invoice with open status and subscription with incomplete status have been generated

#

To handle this, I think an approach would be to detect if the payment fails, grab the invoice and void it (I think when doing this automatically also the subscription will set as expired). This would be correct?

void geyser
#

You need to pay the first invoice within 24 to activate the new subscription, otherwise it will fail as incomplete_expired yes

#

You don't need to void the invoice, you can try paying again if you like

novel quest
#

The problem is that when trying to pay again with a correct card (42424242) the payment is succesful

#

but another subscription is created

#

So the user would have an active subscription and incomplete subscription. How could I handle this? This the main situation I've always been asking to myself how to handle it in a correct way

That I've been doing is if the payment fails, then I void the invoice and the subscription is expired, and then the user tries again until the payment is successfull an dthe sub is active

#

but that requieres a lot of code and logic

void geyser
#

That is likely because your code is creating a new subscription rather than retrying, you need to modify your logic to handle a retry flow

novel quest
#

I have this method

public function createSubscription(Request $request)
    {
        $request->validate([
            'business_quantity' => 'required|numeric|min:1',
            'employees_quantity' => 'required|numeric|min:0',
            'period' => 'required|in:month,year',
            'coupon' => 'nullable',
        ]);

        $user = User::find(auth()->id());

        // Seteamos el priceId del plan base
        $planPriceId = $request->period === 'year' ? config('app.default_base_year') : config('app.default_base_month');
        $employeesPriceId = $request->period === 'year' ? config('app.addon_employees_year') : config('app.addon_employees_month');

        $subscription = $this->stripe->subscriptions->create([
            'customer' => $user->stripe_id,
            'items' => [
                ['price' => $planPriceId, 'quantity' => $request->business_quantity],
                ['price' => $employeesPriceId, 'quantity' => $request->employees_quantity],
            ],
            'proration_behavior' => 'always_invoice',
            'payment_behavior' => 'default_incomplete',
            'coupon' => $request->coupon,
            'payment_settings' => ['save_default_payment_method' => 'on_subscription'],
            'expand' => ['latest_invoice.payment_intent', 'pending_setup_intent'],
        ]);

        if ($subscription->pending_setup_intent !== NULL) {
            return response()->json([
                'type' => 'setup',
                'clientSecretStripe' => $subscription->pending_setup_intent->client_secret
            ]);
        } else {
            return response()->json([
                'type' => 'payment',
                'clientSecretStripe' => $subscription->latest_invoice->payment_intent->client_secret
            ]);
        }
    }
#

Is there any documentation on how to handle this?

void geyser
#

Not specifically, no. You'd need to decide when you're in a "retry" scenario and use the payment intent from the existing sub/invoice

novel quest
#

alright! working on it

novel quest
#

This would be the correct way to approach this? Passing again the payment intent (client secret)?

void geyser
#

Yep, that looks viable!

#

You'll need to test extensively for the customer flows you want to support

#

(I can't say this 100% does what you want, only validating that a "mode" switch like you've implemented to create vs retrieve the sub seems like a good starting point)

novel quest
#

perfect thank you so much @void geyser ! I'm going to test it

void geyser
#

NP!

novel quest
#
const {error} = await confirmIntent({
        elements,
        clientSecret: clientSecretStripe,
        confirmParams: {
            return_url: succesUrl,
        },
    });

#

Do you know if here, or if it would be a way to fill billing details data, when adding the payment method?

void geyser
#

Do you mean filling in additional billing details you collected?

novel quest
#

yes, doing this for example:

const {error} = await confirmIntent({
        elements,
        clientSecret: clientSecretStripe,
        confirmParams: {
            payment_method_data: {
                billing_details: {
                    name: name.value,
                    address: { city: city.value, line1: line1.value, postal_code: postalCode.value, state: state.value }
                }
            },
            return_url: succesUrl,
        },
    });
#

sorry I don't find documentation for confirmIntent method

void geyser
#

confirmIntent is your own abstraction of confirmPayment and confirmSetup

#

So those same parameter apply, yes

void geyser
candid coveBOT
novel quest
#

True, thank you!

novel quest
#

I have some problems

lean umbra
#

Hey there 👋 can you tell me more?

novel quest
#
 const {error} = await confirmIntent({
        elements,
        clientSecret: clientSecretStripe,
        confirmParams: {
            payment_method_data: {
                billing_details: {
                    name: name.value,
                    address: { city: city.value, line1: line1.value, postal_code: postalCode.value, state: state.value }
                }
            },
            return_url: succesUrl,
        },
    });

I have added that, but when the payment is successful the data is not filling to billing details

#

Another problem is that I saw taxes weren't being calculated

#

for that reason I have added this:

try {
                $subscription = $this->stripe->subscriptions->create([
                    'customer' => $user->stripe_id,
                    'items' => [
                        ['price' => $planPriceId, 'quantity' => $request->business_quantity],
                        ['price' => $employeesPriceId, 'quantity' => $request->employees_quantity],
                    ],
                    'proration_behavior' => 'always_invoice',
                    'payment_behavior' => 'default_incomplete',
                    'coupon' => $request->coupon,
                    'automatic_tax' => ['enabled' => true],
                    'payment_settings' => ['save_default_payment_method' => 'on_subscription'],
                    'expand' => ['latest_invoice.payment_intent', 'pending_setup_intent'],
                ]);
            } catch (InvalidRequestException $e) {
                Log::error($e->getMessage(), ['user' => $user->stripe_id]);
                return response()->json([
                    'error' => 'No se ha podido procesar tu pago. Comprueba tus datos e inténtalo de nuevo.',
                ], 400);
            }
#

'automatic_tax', but I am getting always this error:

The customer's location isn't recognized. Set a valid customer address in order to automatically calculate tax.

#

Even if I put a real address

lean umbra
novel quest
#

I send you some screenshots!

lean umbra
#

I'm trying to hint about things that you should be checking too, I don't need everthing in screenshots.

novel quest
#

these variables are coming from the html view, they have data and there's validation to check if they are empty or not. If they are, then you cannot press the payment button

#

the problem is that, when the customer pays

#

they don't have any billing details

#

so because they don't have, Stripe triggers the error "The customer's location isn't recognized"

#

I think

lean umbra
#

Do you have the ID of the request where you're see the location error being returned? That should have a req_ prefix.

novel quest
#

Of course! req_sbCbCjWpIWFjZK

#

subscription is being created after confirmIntent, when that logic happen the customer doesn't have any billing details

#

so I cath that error, and confirmIntent is not being executed because of that

lean umbra
#

You can't create the Subscription after confirmIntent, the Subscription has to be created to generate the clinet secret used for the confirmation.

novel quest
#

sorry

lean umbra
#

The Customer needs to have an address provided before you make the request to create the Subscription.

novel quest
#

I mean before, excuse me

#

Not english native

lean umbra
#

You'll need to collect those address details and use them to update the Customer object before creating the Subscription.

novel quest
#

perfect I think I could create an endpoint, and before subscription creation

#

update customer to fill that data

novel quest
#

perfect got it, I update the customer billing details before subscriptions

#

but still getting the error

#

request of updating customer billing details: req_tX3RSIa9fQjXIe

#

request of subscription creation: req_Md7cqTYtWQfm1y

lean umbra
#

I think that city/zip don't match?

#

Hm, or maybe country is missing so I assumed this was for the US