Code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<form id="payment-form" action="." method="post">
<input type="hidden" name="csrfmiddlewaretoken" value="06dAlJhwvSjVK7u4CZcyAbsa9Ikn9EYsiFGxFpA8z3W2wsr0UMKb8KIxNmYbyFYg">
<p><label for="email">Email:</label> <input type="email" name="payment_email" value="[email protected]" class="form-control" autocomplete="off" id="email" readonly="readonly" required></p>
<p><label for="phone">International Phone:</label> <input type="tel" name="payment_phone" class="form-control" autocomplete="off" id="phone" placeholder="International Phone" required></p>
<p><label for="cardholder-name">Full Name:</label> <input type="text" name="payment_name" class="form-control" autocomplete="off" id="cardholder-name" placeholder="Full Name" required></p>
<p><label for="line1">Address Line 1:</label> <input type="text" name="payment_address1" class="form-control" autocomplete="off" id="line1" placeholder="Address 1" required></p>
<p><label for="line2">Address Line 2:</label> <input type="text" name="payment_address2" class="form-control" autocomplete="off" id="line2" placeholder="Address 2"></p>
<p><label for="city">City:</label> <input type="text" name="payment_city" class="form-control" autocomplete="off" id="city" placeholder="City" required></p>
<p><label for="state">State or Province:</label> <input type="text" name="payment_state" class="form-control" autocomplete="off" id="state" placeholder="State or Province" required></p>
<p><label for="country">2 Letter Country Code (<a href="https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements" target="_blank" rel="noopener noreferrer nofollow">Find Your Country Code</a>):</label> <input type="text" name="payment_country" class="form-control" autocomplete="off" id="country" maxlength="2" placeholder="Country Code" required></p>
<script nonce="tKD42vLlpXoOTvs+kP4PUQ==" src="https://hcaptcha.com/1/api.js" async defer></script>
<script nonce="tKD42vLlpXoOTvs+kP4PUQ==">
function onSubmit(token) {
if ( window.history.replaceState ) {
window.history.replaceState( null, null, window.location.href );
}
var form = document.getElementById("payment-form");
document.getElementById("card-button").disabled = true;
form.submit();
if (event.error) {
document.getElementById("card-button").disabled = false;
}
}
</script>
<label>
Credit or Debit Card:
</label>
<div id="card-element" class="form-control stripe_card_padding">
<!-- A Stripe Element will be inserted here. -->
</div>
<!-- Used to display form errors. -->
<div id="card-errors" class="stripe_card_errors" role="alert" ></div>
<!-- <div id="stripe-result-handler" class="is-hidden">
Success! Got token: <span class="result"></span>
</div> -->
<button
id="card-button"
type="submit"
class=" form-control payment_button h-captcha"
data-sitekey="8b425911-fa93-429c-a98c-4c56b93b6662"
data-callback="onSubmit"
>
Submit Payment
</button>
</form>
<script nonce="tKD42vLlpXoOTvs+kP4PUQ==" src="https://js.stripe.com/v3/"></script>
<script nonce="tKD42vLlpXoOTvs+kP4PUQ==">
// Create a Stripe client.
var stripe = Stripe("pk_test_tcuxCLB8Y8NN8H3OPcR6ALKh00UjP2WfYt");
// Create an instance of Elements.
var elements = stripe.elements();
var cardButton = document.getElementById("card-button");
var cardholderName = document.getElementById("cardholder-name");
//var cardElement = elements.create("card");
//var cardElement = elements.create("card");
//cardElement.mount("#card-element");
var line1 = document.getElementById("line1");
var line2 = document.getElementById("line2");
var city = document.getElementById("city");
var country = document.getElementById("country");
var email = document.getElementById("email");
var phone = document.getElementById("phone");
var state = document.getElementById("state");
var style = {
base: {
color: 'black',
iconColor: 'black',
fontSize: '15px',
fontFamily: '"Roboto", sans-serif',
fontSmoothing: 'antialiased',
'::placeholder': {
color: 'black',
},
},
invalid: {
color: '',
':focus': {
color: '',
},
},
};
var cardElement = elements.create('card', {style: style});
cardElement.mount('#card-element');
// Add an instance of the card Element into the `card-element` <div>.
// Handle real-time validation errors from the card Element.
cardElement.addEventListener("change", function (event) {
var displayError = document.getElementById("card-errors");
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = "";
}
});
// Handle form submission.
//.value.trim() || null is needed to change the form input to required/optional for data collection. Was originally .value only.
var form = document.getElementById("payment-form");
form.addEventListener("submit", function (event) {
event.preventDefault();
document.getElementById("card-button").disabled = true;
var billingInfo = {
billing_details: {
name: cardholderName.value.trim() || null,
address: {
line1: line1.value.trim() || null,
line2: line2.value.trim() || null,
city: city.value.trim() || null,
state: state.value.trim() || null,
country: country.value.trim() || null,
},
email: email.value,
phone: phone.value.trim() || null,
}
};
stripe.createPaymentMethod('card', cardElement, billingInfo).then(function (result) {
if (result.error) {
// Inform the user if there was an error.
var errorElement = document.getElementById("card-errors");
errorElement.textContent = result.error.message;
document.getElementById("card-button").disabled = false;
} else {
// Send the token to your server.
//setTimeout(stripeSourceHandler(result.paymentMethod.id), 3000);
stripeSourceHandler(result.paymentMethod.id);
}
});
});
// Submit the form with the source ID.
function stripeSourceHandler(payment_method_id) {
var form = document.getElementById("payment-form");
var hiddenInput = document.createElement("input");
hiddenInput.setAttribute("type", "hidden");
hiddenInput.setAttribute("name", "PaymentMethod");
hiddenInput.setAttribute("value", payment_method_id);
form.appendChild(hiddenInput);
// Insert the source ID into the form so it gets submitted to the server
// Submit the form
form.submit();
}
</script>
https://jsfiddle.net/g5nkcL1u/
I am trying to do this with hcaptcha:
https://docs.hcaptcha.com/invisible#automatically-bind-the-challenge-to-a-button
And I am trying to combine it with stripe.js.
I think the problem has to do with data-callback for the button. Since there are 2 separate form.submit(); lines in my code, it does not know which one to run.
My question is, how do I combine my stripe javascript with my hcaptcha javascript to get them to work together?
Errors:
'No such PaymentMethod' -> If I use data-callback="stripeSourceHandler"
'Unrecognized request URL (POST: /v1/payment_methods//attach)' -> if I use data-callback="onSubmit"
I don't believe this has anything to do with my server code. If I delete all hcaptcha code on my client side everything works server side. If I use the regular hcaptcha checkbox widget instead of tying to a button, everything works since there is no data-callback. But I want to tie hcaptcha to a button because it is better UX compared to the checkbox widget.
I use the onSubmit function in my other form webpages and hcaptcha works fine since stripe is not involved in those other form webpages.
Other ideas:
onSubmit does not call the part of the code that creates the Stripe PaymentMethod. Have the creation happen in its own function like callStripe() and then call that PaymentMethod create function after the captcha part.
Call hcaptcha.render() to show the captcha and after that create the PaymentMethod and submit the form
What actually triggers the captcha flow? The
onSubmit
doesn't appear to have anything explicitly connected to that. Otherwise you ought to be able to chain the sequence of 3 blocks together:onSubmit
(or some other function), remove theform.submit()
callstripeSourceHandler
should already be called as a callbackNote that you'll also want to make sure you verify the hcaptca result server-side.
Separately, do you not have a problem with recursion? Your form submit listener (
form.addEventListener("submit", ...)
) ends up callingstripeSourceHandler
which itself callsform.submit()
.