#sergx_docs
1 messages · Page 1 of 1 (latest)
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.
- sergx_docs, 3 days ago, 57 messages
👋 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.
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
]);
}
}
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"
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',
},
});
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)
Can you clarify? I don't understand the question.
Sure, so you can update that amount/currency:
https://docs.stripe.com/js/elements_object/update#elements_update-options-currency
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
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?
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
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
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
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?
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
alright! working on it
This would be the correct way to approach this? Passing again the payment intent (client secret)?
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)
perfect thank you so much @void geyser ! I'm going to test it
NP!
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?
Do you mean filling in additional billing details you collected?
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
confirmIntent is your own abstraction of confirmPayment and confirmSetup
So those same parameter apply, yes
In this snippet you shared you have:
const confirmIntent = type === "setup" ? stripe.confirmSetup : stripe.confirmPayment;
True, thank you!
I have some problems
Hey there 👋 can you tell me more?
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
Is there more context here? Are those variables populated but not being passed, or are they empty/undefined? Where are they coming from?
I send you some screenshots!
I'm trying to hint about things that you should be checking too, I don't need everthing in screenshots.
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
Do you have the ID of the request where you're see the location error being returned? That should have a req_ prefix.
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
You can't create the Subscription after confirmIntent, the Subscription has to be created to generate the clinet secret used for the confirmation.
sorry
The Customer needs to have an address provided before you make the request to create the Subscription.
You'll need to collect those address details and use them to update the Customer object before creating the Subscription.
perfect I think I could create an endpoint, and before subscription creation
update customer to fill that data