It seems you have an integration problem with the JS part. For a simple charge (the following example does not work for subscription), this is the way you have to implement it:
First you have to create a Payment intent (doc here: https://stripe.com/docs/api/payment_intents/create):
\Stripe\PaymentIntent::create([
"amount" => 2000,
"currency" => "usd",
"payment_method_types" => ["card"],
]);
Once your PaymentIntent response is returned, you will have a client_secret key (doc here : https://stripe.com/docs/api/payment_intents/object). You can see that your payment status is "requires_payment_method"
{
"id": "pi_1Dasb62eZvKYlo2CPsLtD0kn",
"object": "payment_intent",
"amount": 1000,
"amount_capturable": 0,
"amount_received": 0,
...
"client_secret": "pi_1Dasb62eZvKYlo2CPsLtD0kn_secret_6aR6iII8CYaFCrwygLBnJW8js",
...
"status": "requires_payment_method",
...
}
On your server side you have to save this object.
You can now show your payment form with the JS part with the previous client_secret key (doc here : https://stripe.com/docs/payments/payment-intents/verifying-status). The idea is that you have to call the Js function on click on the submit button but do not submit ! Wait for the response to submit.
With some jquery this should like this:
var $mySubmitButton = $('#my-submit-button'),
$myPaymentForm = $('#my-payment-form'),
clientSecret = $cardButton.data('client-secret'); // put your client-secret somewhere
$mySubmitButton.on('click', function(e) {
e.preventDefault();
// Disable button to disallow multiple click
$(this).attr("disabled", true);
stripe.handleCardPayment(
clientSecret,
{
payment_method: clientMethod, // if you have a clientMethod
}
).then(function(result) {
if (result.error) {
// show error message
// then enable button
$mySubmitButton.attr("disabled", false);
} else {
// Submit form
$myPaymentForm.submit();
}
});
});
If everything goes right, when you click your submit button, you will have a test 3D secure popin with the options to "success" or "fail" the security test. If you click on success button, your form is submitted and you have to wait for the webhook "charged.success" to confirm transaction.
Once received, change you server object status and notify user about the transaction.
In my case, once the form submitted, I show a loader and check with ajax call every second to see if my payment intent status have changed (through the webhook). For your test environnement, you can use http://requestbin.net and Postman.
Beware: some cards referenced on this page will not work properly (you can't add them) https://stripe.com/docs/testing#cards (section 3D Secure test card numbers and tokens). Confirmed with their support.
If you work with saved card, you can test only with these cards: https://stripe.com/docs/payments/cards/charging-saved-cards#testing