Stripe.js payment does not work for some customers only - javascript

I have a php (Laravel 7) website that uses Stripe.js to accept credit card payments. The integration usually works nicely, but from time to time, I have some customers, who are not able to make a payment. There is no error message, all I know from the customers is something like this:
"I entered the card details, clicked "Pay" and then a white field and nothing..."
On the stripe backend, I see the payment with status payment "Incomplete" and the "Customer has not entered its payment method". I believe this means that the customer came to the payment page, but has not (could not?) finished the payment for some reason.
Please see some code excerpts below. The first code shows how I create a payment intent when the payment page is loaded. The second one shows the payment form and js code to make the payment. My suspicion is that somehow the js code breaks down for some customers...
Any ideas why this is not working for some customers or how I could debug the root-cause?
Thanks,
W.
GNGBookingController.php:
function show_step4($id){
// Create and send payment intent
\Stripe\Stripe::setApiKey(env('STRIPE_SECRET'));
$idstr = '[T#' . sprintf('%04d', $tour->id) . ']';
try{
$intent = \Stripe\PaymentIntent::create([
'amount' => round($tour->fees->plat_fee * 100),
'currency' => 'eur',
'payment_method_types' => ['card'],
'description' => $idstr,
]);
}
catch(\Exception $e) {
GNGLogger::qlog('Failed to create payment intent. ' . $e->getFile() . ' ' . $e->getLine() . $e->getMessage(), GNGLogType::Error);
redirect(route('office.' . app::getLocale() . '.crash.show'));
}
return view('backoffice.booking.ver2.show-step4', [
'tour' => $tour,
'clientSecret' => $intent->client_secret,
'date' => $date,
'time' => $time,
]);
}
show-step4.blade.php:
#extends('backoffice.layouts.guest')
#section('content')
<script src="https://js.stripe.com/v3/"></script>
<div class="row">
<div class="col-sm-1 col-md-2 col-lg-3 col-xl-4">
</div>
<div class="col">
<h3>{{__('Advance Payment')}}</h3>
<form id="payment-form" method="post" data-secret="{{$clientSecret}}">
<input type="hidden" name="token" />
<div id="payment-form-holder" class="my-4 p-3 border rounded shadow" style="background-color: lightgrey">
#csrf
<div class="form-group">
<input type="hidden" class="form-control" name="ID" id="ID" value="{{$tour->id}}">
</div>
<div class="form-group">
<label>{{__('Card Number')}}</label><br>
<div id="card-number">
</div>
</div>
<div class="form-row">
<div class='col'>
<div class="form-group">
<label>{{__('Expiry Date')}}</label><br>
<div id="card-expiry">
</div>
</div>
</div>
<div class='col'>
<label>{{__('Security Code')}}</label><br>
<div class="form-group">
<div id="card-cvc">
</div>
</div>
</div>
</div>
<!-- We'll put the error messages in this element -->
<div id="card-errors" role="alert"></div>
<div class="form-row my-3" style="flex-wrap: nowrap">
<div class="spinner-border text-danger" id="spinner" role="status" style="display: none;">
</div>
<div id="spinner-text" class="mx-2" style="display: none;">
{{__('Please wait. This can take up to a minute...')}}
</div>
</div>
<p id="card-error" role="alert"></p>
<p id="result-message" class="result-message" style="display: none;">
{{__('Payment succeeded!')}}
</p>
<div class="form-row">
<input type="submit" id="pay" name="pay" class="btn btn-primary gng-action-button mr-3 mb-3" style="min-width: 100%" value="{{__('Pay :platFee €', ['platFee' => $tour->fees->plat_fee])}}" >
</div>
</div>
<input type="submit" id="back" name="back" class="btn btn-secondary gng-action-button mr-3 mb-3" value="{{__('Back')}}" >
</form>
</div>
<div class="col-sm-1 col-md-2 col-lg-3 col-xl-4">
</div>
</div>
#endsection
#section('scripts')
<script>
// Create a Stripe client
var stripe = Stripe('{{env('STRIPE_KEY')}}');
// Set up Stripe.js and Elements to use in checkout form
var elements = stripe.elements();
var style = {
base: {
color: "#32325d",
fontSize: "18px",
}
};
var classes = {
base: 'form-control',
};
var cardNumberElement = elements.create('cardNumber', {
style: style,
classes: classes,
});
cardNumberElement.mount('#card-number');
var cardExpiryElement = elements.create('cardExpiry', {
style: style,
classes: classes,
});
cardExpiryElement.mount('#card-expiry');
var cardCvcElement = elements.create('cardCvc', {
style: style,
classes: classes,
});
cardCvcElement.mount('#card-cvc');
document.querySelector('#payment-form').addEventListener('submit', function(e) {
if (e.submitter.name == 'pay') {
e.preventDefault();
loading(true);
stripe
.confirmCardPayment('{{$clientSecret}}', {
payment_method: {
card: cardNumberElement,
billing_details: {
name: '{{$tour->guest->name}}'
}
}
})
.then(function(result) {
if (result.error) {
// Show error to your customer
showError(result.error.message);
} else {
// The payment succeeded!
orderComplete(result.paymentIntent.id);
stripe.createToken(cardNumberElement, options).then(submitToken);
}
});
var options = {
//address_zip: document.getElementById('postal-code').value,
};
}
});
function submitToken(result) {
var form = document.querySelector('#payment-form');
form.querySelector('input[name="token"]').setAttribute('value', result.token.id);
form.submit();
}
/* ------- UI helpers ------- */
// Shows a success message when the payment is complete
var orderComplete = function(paymentIntentId) {
loading(false);
document.querySelector(".result-message").style.display = 'flex';
document.querySelector("#pay").disabled = true;
document.querySelector("#back").disabled = true;
};
// Show the customer the error from Stripe if their card fails to charge
var showError = function(errorMsgText) {
loading(false);
var errorMsg = document.querySelector("#card-error");
errorMsg.textContent = errorMsgText;
setTimeout(function() {
errorMsg.textContent = "";
}, 4000);
};
// Show a spinner on payment submission
var loading = function(isLoading) {
if (isLoading) {
// Disable the button and show a spinner
document.querySelector("#pay").disabled = true;
document.querySelector("#back").disabled = true;
document.querySelector("#spinner").style.display = 'flex';
document.querySelector("#spinner-text").style.display = 'flex';
document.querySelector("#pay").style.display = 'none';
document.querySelector("#back").style.display = 'none';
} else {
document.querySelector("#pay").disabled = false;
document.querySelector("#back").disabled = false;
document.querySelector("#spinner").style.display = 'none';
document.querySelector("#spinner-text").style.display = 'none';
document.querySelector("#pay").style.display = 'inline-block';
document.querySelector("#back").style.display = 'inline-block';
}
};
</script>
#endsection

If it is only breaking for some customers, I suspect this is a device/browser compatibility issue. Whenever this happens, be sure to ask what device and browser they are using when submitting payment. Then, you can verify if they are using a stripe.js compatible browser. If they are using a stripe.js compatible browser, my recommendation is to install whatever browser they are using and test your flow (while inspecting the developer console for errors).

Related

Using tagify input elements in form-repeater

I want to use form repeater on my page.
In these repeating forms, there is a tagify input element.
In the first form, the tagify element works correctly, but when I click on the add form-repeater button. A new form is created, but the tagify input element does not work for the new form and subsequent forms.
Please guide me...
this is my "Tagify" js file :
`
/**
* Tagify
*/
"use strict";
(function () {
// Custom list & inline suggestion
//--------------------------------
let TagifyCustomListSuggestionEl = document.querySelectorAll('input[name="form_repeater_categories[]"]');
const whitelist = [];
// List
TagifyCustomListSuggestionEl.forEach((e) => {
console.log("yes");
new Tagify(e, {
whitelist: whitelist,
maxTags: 13,
dropdown: {
maxItems: 20,
classname: "",
enabled: 0,
closeOnSelect: false,
},
});
});
})();
`
and this is my "Form-Repeater" js file :
`
// bootstrap-maxlength & repeater (jquery)
$(function() {
var formRepeater = $('.form-repeater');
// Form Repeater
// ! Using jQuery each loop to add dynamic id and class for inputs. You may need to improve it based on form fields.
// -----------------------------------------------------------------------------------------------------------------
if (formRepeater.length) {
var row = 2;
var col = 1;
formRepeater.on('submit', function(e) {
e.preventDefault();
});
formRepeater.repeater({
show: function() {
var fromControl = $(this).find('.form-control, .form-select');
var formLabel = $(this).find('.form-label');
fromControl.each(function(i) {
var id = 'form-repeater-' + row + '-' + col;
$(fromControl[i]).attr('id', id);
$(formLabel[i]).attr('for', id);
col++;
});
row++;
$(this).slideDown();
},
hide: function(e) {
confirm('Are you sure to delete this form?') && $(this).slideUp(e);
}
});
}
});
`
and this is my HTML file:
`
<div class="form-repeater">
<div data-repeater-list="Partitions-Group">
<div data-repeater-item>
<div class="row">
<div class="mb-3 col-lg-6 col-xl-3 col-12 mb-0">
<label class="form-label" for="form-repeater-1-1">PartNumber:<small class="text-danger">*</small></label>
<input type="number" name="form_repeater_partnum" id="form-repeater-1-1" class="form-control text-start" dir="rtl" placeholder="1">
</div>
<div class="mb-3 col-lg-6 col-xl-3 col-12 mb-0">
<label class="form-label" for="form-repeater-1-2">Details:<small class="text-danger">*</small></label>
<input type="number" name="form_repeater_partcellnum" id="form-repeater-1-2" class="form-control text-start" dir="rtl" placeholder="10">
</div>
<!-- Custom Suggestions: List -->
<div class="mb-3 col-lg-12 col-xl-10 col-12 mb-0">
<label for="form-repeater-1-3" class="form-label">Categories:<small class="text-danger">*</small></label>
<input name="form_repeater_categories" id="form-repeater-1-3" class="form-control form_repeater_categories" dir="rtl" placeholder="SelectCategories..." value="">
</div>
<div class="mb-3 col-lg-12 col-xl-2 col-12 d-flex align-items-center mb-0">
<span class="btn btn-label-danger mt-4" data-repeater-delete>
<i class="bx bx-x"></i>
<span class="align-middle">Delete</span>
</span>
</div>
</div>
</div>
</div>
<div class="mb-0">
<span class="btn btn-primary" data-repeater-create>
<i class="bx bx-plus"></i>
<span class="align-middle">Add Form</span>
</span>
</div>
</div>
`
I used the source above and expected Tagify to run on the input element named "form_repeater_categories" in the next repeating forms that are created by clicking the "Add Form" button. But it runs only on the first form of the Tagify library.
I want the Tagify library to work well in future recurring forms as well.

How to display error on the same page when an error is returned by a form?

I have built a login form using below code.
I am using a CGI version CGIDEV2 native to IBMi series. I am Validating the userid and password in the flower.cgi program. If the userid and password are validated, I am loading another html file to show a table. This works flawlessly.
If the userid and password are wrong, I am returning out of the program without writing anything. This results in a 500 Internal server error.
I want to capture this 500 Internal server error using javascript. I have tried using ajax but was not successful as I have limited understanding of javascript.
What could be the best way to achieve this?
<form method="POST" action="/nature/flower.cgi">
<!-- Username input -->
<div class="form-outline mb-4">
<input type="text" name="userid" id="form3Example3" class="form-control form-control-lg" style="text-transform:uppercase" placeholder="Enter a valid IBMi UserID" />
<label class="form-label" for="form3Example3">IBMi UserID</label>
</div>
<!-- Password input -->
<div class="form-outline mb-3">
<input type="password" name="passwd" id="form3Example4" class="form-control form-control-lg" placeholder="Enter password" />
<label class="form-label" for="form3Example4">Password</label>
<br>
</div>
<div class="d-flex justify-content-between align-items-center">
<!-- Checkbox -->
<div class="form-check mb-0">
<input class="form-check-input me-2" type="checkbox" value="" id="form2Example3" />
<label class="form-check-label" for="form2Example3">Remember me</label>
</div>
Forgot password?
</div>
<div class="text-center text-lg-start mt-4 pt-2">
<button type="submit" class="btn btn-primary btn-lg" style="padding-left: 2.5rem; padding-right: 2.5rem;">Login</button>
</div>
</form>
You can't capture an error from a URL the browser is navigating to.
The only way you could would be if you replaced the normal form submission with Ajax (in which the request is made and the response processed with JavaScript).
Generally speaking, if you were going to do that you would also want to rewrite the CGI program so it output structured data (e.g. as JSON) instead of semantic data (HTML).
It would not be a particularly small undertaking. You said you had tried it, which probably gives you some idea of the scope of it (i.e. far outside the scope of a Stackoverflow question).
A more sensible approach would almost certainly be to track down the cause of the 500 error and change the CGI program so it would capture it itself.
yes, the fetch api in javascript can post form data to your CGI program and check for the 500 response code. Then the web page can either display an error message or run the form.submit() method to actually submit the form up to the server.
Here is PHP and javascript code that simulates the whole thing. Should work similar with CGI. The key is you change the form submit button from type 'submit' to type 'button'. That way, clicking the submit button can run javascript code instead of the form being submitted to the server.
<?php
// site/tester/php/submit-form.php
header("Content-type: text/html; charset:utf-8;");
?>
<?php
$userid = isset($_POST["userid"]) ? $_POST["userid"]: '' ;
$passwd = isset($_POST["passwd"]) ? $_POST["passwd"]: '' ;
$remember = isset($_POST["remember"]) ? $_POST["remember"]: 'off' ;
if ( strtolower($userid) == 'alex')
{
http_response_code( 500 );
echo "$userid - invalid user name";
exit ;
}
?>
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row mt-3">
<div class="col-auto">
<h1>submit form demo</h1>
</div>
</div>
</div>
<div class="container">
<div class="row mt-3">
<div class="col-4">
<form method="POST" id="main-form" action="./index.php">
<!-- Username input -->
<div class="form-outline mb-4">
<input type="text" name="userid" id="userid"
class="form-control form-control-lg"
value="<?= $userid ?>"
style="text-transform:uppercase" placeholder="Enter a valid IBMi UserID" />
<label class="form-label" for="userid">IBMi UserID</label>
</div>
<!-- Password input -->
<div class="form-outline mb-3">
<input type="password" name="passwd" id="passwd" class="form-control form-control-lg"
placeholder="Enter password" />
<label class="form-label" for="passwd">Password</label>
<br>
</div>
<div class="d-flex justify-content-between align-items-center">
<!-- Checkbox -->
<div class="form-check mb-0">
<input class="form-check-input me-2" type="checkbox"
<?= ($remember == 'on' ? 'checked' : '' ) ?>
name="remember" id="form2Example3" />
<label class="form-check-label" for="form2Example3">Remember me</label>
</div>
Forgot password?
</div>
<div class="text-center text-lg-start mt-4 pt-2">
<button id="login-button" type="button" class="btn btn-primary btn-lg"
style="padding-left: 2.5rem; padding-right: 2.5rem;">
Login</button>
</div>
</form>
</div>
</div>
<div id="login-errmsg-collapse" class="collapse row mt-3">
<div class="alert alert-warning" role="alert">
<p id="login-errmsg">Invalid login. User name does not exist.</p>
</div>
</div>
</div>
<div class="container">
<div class="row mt-3">
<div class="col-auto">
<p>userid: <?= $userid ?></p>
<p>passwd: <?= $passwd ?></p>
<p>remember: <?= $remember ?></p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.0.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
crossorigin="anonymous"></script>
<script src="./app.js"></script>
</body>
// ./submit-form/app.js
// enable the submit button click handler.
{
const elem = document.getElementById('login-button');
elem.addEventListener('click', event =>
{
login_click();
});
}
// -------------------------------- login_errmsg_show ---------------------
function login_errmsg_show( respText )
{
// show the response message in the bootstrap collapse alert box.
{
const elem = document.getElementById('login-errmsg') ;
elem.textContent = respText ;
}
const myCollapse = document.getElementById('login-errmsg-collapse')
const bsCollapse = new bootstrap.Collapse(myCollapse, {
toggle: false
});
bsCollapse.show( ) ;
}
// -------------------------------- login_click -------------------
async function login_click()
{
const elem = document.getElementById('main-form');
if (elem)
{
const {respText, status} = await webpage_post( ) ;
if ( status != '500')
{
elem.submit();
}
else
{
login_errmsg_show( respText ) ;
}
}
}
// --------------------------------- webpage_post -----------------------
async function webpage_post( )
{
const userid = document.getElementById('userid').value ;
const passwd = document.getElementById('passwd').value ;
const remember = 'on' ;
const url = "./index.php";
const params = { userid, passwd, remember } ;
const query = object_toQueryString(params);
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: query
});
const respText = await response.text();
const status = response.status ;
console.log( respText ) ;
return {respText, status} ;
}
// ------------------------- object_toQueryString ------------------------
function object_toQueryString(obj)
{
const qs = Object.keys(obj)
.map((key) => encodeURIComponent(key) + '=' + encodeURIComponent(obj[key]))
.join('&');
return qs;
}

VUE JS - Some of my methods are being called without me calling them

I'm having an annoying issue with Vue JS >.< My methods are being called without my approval. I basically have a button that execute a method, but this method execute when other methods are executed, making it very annoying...
Here is my form
<form class="row">
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 pr-xl-0 pr-lg-0 pr-md-0 m-b-30">
<div class="product-slider">
<img class="d-block" :src="image" alt="First slide" width="285" height="313">
Image URL <input type="text" #focusout="showPicture" id="imageLink">
</div>
</div>
<div class="col-xl-6 col-lg-6 col-md-6 col-sm-12 col-12 pl-xl-0 pl-lg-0 pl-md-0 border-left m-b-30">
<div class="product-details">
<div class="border-bottom pb-3 mb-3">
<h2 class="mb-3">
<input type="text" value="Product Name" minlength="4" id="name" required/>
</h2>
<h3 class="mb-0 text-primary">$<input type="number" value="1.00" step="0.01" id="price" required></h3>
</div>
<div class="product-size border-bottom">
<h4>Provider</h4>
<input type="text" value="Pro Inc." minlength="3" id="provider" required>
<div class="product-qty">
<h4>Quantity</h4>
<div class="quantity">
<input type="number" value="1" id="quantity" required>
</div>
</div>
</div>
<div class="product-description">
<h4 class="mb-1">Description</h4>
<textarea rows="4" cols="50" minlength="50" id="description" required>Sample Text</textarea>
<button :onclick="addProduct()" class="btn btn-primary btn-block btn-lg">Add to inventory</button>
</div>
</div>
</div>
</form>
and here is my full script
<script>
const DB_NAME = 'DBInventory';
const DB_VERSION = 1;
export default {
data: function() {
return {
db:null,
ready:false,
addDisabled:false,
image: "https://i.imgur.com/O9oZoje.png",
};
},
async created() {
this.db = await this.getDb();
this.ready = true;
},
methods: {
showPicture() {
let link = document.getElementById("imageLink").value;
if(link !== "")
this.image = link;
},
async getDb() {
return new Promise((resolve, reject) => {
let request = window.indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = e => {
console.log('Error opening db', e);
reject('Error');
};
request.onsuccess = e => {
resolve(e.target.result);
};
request.onupgradeneeded = e => {
console.log('onupgradeneeded');
let db = e.target.result;
let objectStore = db.createObjectStore("products", { autoIncrement: true, keyPath:'id' });
console.log(objectStore);
};
});
},
async addProduct() {
this.addDisabled = true;
let product = {
name: document.getElementById("name").value,
provider: document.getElementById("provider").value,
price: document.getElementById("price").value,
quantity: document.getElementById("quantity").value,
description: document.getElementById("description").value,
image: document.getElementById("imageLink").value,
};
console.log('about to add '+JSON.stringify(product));
await this.addProductToDb(product);
this.addDisabled = false;
},
async addProductToDb(product) {
return new Promise((resolve, reject) => {
//delete me
console.log(reject);
let trans = this.db.transaction(['products'],'readwrite');
trans.oncomplete = e => {
//delete me
console.log(e);
resolve();
};
let store = trans.objectStore('products');
store.add(product);
});
},
}
}
</script>
One of my method execute when you are not focused on the image input field. It works, but also execute the addProduct(), which push my item to my indexDB, something that I want to happen only when I press the button "Add to inventory".
This is very confusing and I'm kinda a noob on Vue JS ^^' (I use Vue 3)
You have the wrong syntax
:onclick="addProduct()"
:onclick should be #click or v-on:click
https://v2.vuejs.org/v2/guide/events.html
To avoid the function to be autoexecuted, you need to provide the onclick method with the function declaration. Try the code below. I don't use Vue, but it works in React. Javascript anyway.
:onclick="() => addProduct()"
Correct syntax for click event in vue
full syntax
<a v-on:click="addProduct"></a>
shorthand syntax
<a #click="addProduct"></a>
Then call method addProduct
addProduct: function () {
...
}

Node js jquery ajax

I have the following the code on aap.js with a route to '/bio_post'. I'm trying to post some text based on my website using jquery ajax. The problem I'm having is that jquery picks up the objects that come from '/bio_post' as undefined. I was wondering if you guys can point me in the right direction. Thank you for your time and effort.
app.post('/bio_post', function (req, res) {
// Get user input value
let update_bio = req.body.user_bio;
// Check if the input isn't empty
if(update_bio !== "") {
// Connect to mysql database
mysql_connection.query("UPDATE users SET user_bio = ? WHERE id = ?", [update_bio, session_id], function (err, rows) {
if (err) {
let obj_error = {err: err};
res.send(obj_error.err);
} else {
// Check if the post was successfully inserted into the database
if (rows.changedRows === 1) {
let id = {id: session_id};
res.redirect("/profile/" + JSON.stringify(id.id));
console.log("bio post id is: " + JSON.stringify(id.id));
/*let msg_success = {id: session_id, success_msg: 'Bio successfully inserted!'};
res.send(msg_success);*/
} else {
let id = {id: session_id};
let msg_error = {msg_err: 'No changes were made to your bio.'};
res.send(JSON.stringify(msg_error.msg_err));
console.log("bio post id is: " + JSON.stringify(id.id));
}
}
});
}else{
//res.render("settings", {u_id: session_id, logged_in_user: session_username, bio: '', bio_msg: "Bio field cannot be empty."});
res.send("Bio field is empty");
}
});
Below is the code for jquery.
/**Set up URL route variable**/
let url = '/bio_post';
/**Set up Ajax callback**/
$.ajax({
url: url,
type: 'POST',
data: {user_bio: user_bio.val()},
success: function (data) {
if(data.msg_err){
success_bio_handler.hide();
error_bio_handler.fadeIn().text(data.msg_err).delay(5000).fadeOut();
} else {
error_bio_handler.hide();
//success_bio_handler.fadeIn().text(data.success_msg).delay(5000).fadeOut();
//window.location.href = '/profile/' + data.id;
console.log(data.id); /***COMES OUT AS UNDEFINED****/
}
}
});
Then lastly, is the html file.
<!--ADD OR UPDATE BIO-->
<div id="add_update_bio_title"><strong>Add or Update Bio</strong></div>
<div id="add_update_bio_panel">
<form id="frm_add_update_bio" action="/bio_post" method="post"><br />
<div class="py-5 w-100">
<div class="row">
<div class="col-md-4"></div>
<div class="col-md-4">
<p id="error_bio_handler" class="alert-danger collapse">Error</p><!--jQuery error handler-->
<p id="success_bio_handler" class="collapse" style="background-color: #00AA88; color: white;"></p><!--jQuery success handler-->
<form class="">
<div class="form-group">
<!--Check if user has entered a bio-->
<div class="alert-danger"><%= typeof bio_msg !== 'undefined' ? bio_msg : '' %></div><br />
<% if (bio){%>
<textarea class="form-control" rows="5" id="user_bio" name="user_bio" placeholder="Write a little something about yourself"><%- bio%></textarea>
<%} else{%>
<textarea class="form-control" rows="5" id="user_bio" name="user_bio" placeholder="Write a little something about yourself"></textarea>
<%}%>
</div>
<div class="form-group"></div>
<div class="form-group"></div>
<button id="btnSubmitBio" type="button" class="btn btn-primary">Add or Update Bio</button>
</form>
</div>
<div class="col-md-4"></div>
</div>
</div>
</form>
</div>
<!--END ADD OR UPDATE BIO-->

Trying to send custom inputs to braintree but only payment_method_nonce works

I am attempting to have the user inputs( first name, last name, contract number, amount) pass to payment.php and then be sent via $result = Braintree_Transaction::sale but payment_method_nonce is the only thing that passes to payment.php. When test payment.php with <?php print_r($_POST); ?> I only receive Array ( [payment_method_nonce] => 9ce4f24f-9746-076c-760b-d30d18a3cdf5 ) Thanks in advance Here is my code:
HTML
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default bootstrap-basic">
<div class="panel-heading">
<h3 class="panel-title">Enter Payment Details</h3>
</div>
<form class="panel-body" id="paymentportal" action="payment.php" method="post">
<div class="row">
<div class="form-group col-sm-6">
<label class="control-label">First Name</label>
<!-- Hosted Fields div container -->
<input type="text" placeholder="John" class="form-control" id="first-name">
</div>
<div class="form-group col-sm-6">
<label class="control-label">Last Name</label>
<!-- Hosted Fields div container -->
<input type="text" placeholder="Doe" class="form-control" id="last-name">
</div>
</div>
<div class="row">
<div class="form-group col-sm-6">
<label class="control-label">Contract Number</label>
<!-- Hosted Fields div container -->
<input type="text" placeholder="1462" class="form-control" id="order-number">
</div>
<div class="form-group col-sm-6">
<label class="control-label">Amount</label>
<!-- Hosted Fields div container -->
<input type="text" placeholder="$1234.56" class="form-control" id="amount">
</div>
</div>
<div class="row">
<div class="form-group col-sm-8">
<label class="control-label">Card Number</label>
<!-- Hosted Fields div container -->
<div class="form-control" id="card-number"></div>
<span class="helper-text"></span>
</div>
<div class="form-group col-sm-4">
<div class="row">
<label class="control-label col-xs-12">Expiration Date</label>
<div class="col-xs-6">
<!-- Hosted Fields div container -->
<div class="form-control" id="expiration-month"></div>
</div>
<div class="col-xs-6">
<!-- Hosted Fields div container -->
<div class="form-control" id="expiration-year"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="form-group col-sm-6">
<label class="control-label">Security Code</label>
<!-- Hosted Fields div container -->
<div class="form-control" id="cvv"></div>
</div>
<div class="form-group col-sm-6">
<label class="control-label">Zipcode</label>
<!-- Hosted Fields div container -->
<div class="form-control" id="postal-code"></div>
</div>
</div>
<input type="hidden" name="payment_method_nonce" id="payment_method_nonce">
<button value="btnSubmit" id="btnSubmit" class="btn-box center-block">Pay with <span id="card-type">Card</span></button>
</form>
</div>
</div>
JS
var form = document.getElementById('paymentportal');
braintree.client.create({
authorization: 'sandbox_authorization'
}, function (err, clientInstance) {
if (err) {
console.error(err);
return;
}
braintree.hostedFields.create({
client: clientInstance,
styles: {
'input': {
'font-size': '14px',
'font-family': 'helvetica, tahoma, calibri, sans-serif',
'color': '#3a3a3a'
},
':focus': {
'color': 'black'
}
},
fields: {
number: {
selector: '#card-number',
placeholder: '4111 1111 1111 1111'
},
cvv: {
selector: '#cvv',
placeholder: '123'
},
expirationMonth: {
selector: '#expiration-month',
placeholder: 'MM'
},
expirationYear: {
selector: '#expiration-year',
placeholder: 'YY'
},
postalCode: {
selector: '#postal-code',
placeholder: '90210'
}
}
}, function (err, hostedFieldsInstance) {
if (err) {
console.error(err);
return;
}
hostedFieldsInstance.on('validityChange', function (event) {
var field = event.fields[event.emittedBy];
if (field.isValid) {
if (event.emittedBy === 'expirationMonth' || event.emittedBy === 'expirationYear') {
if (!event.fields.expirationMonth.isValid || !event.fields.expirationYear.isValid) {
return;
}
} else if (event.emittedBy === 'number') {
$('#card-number').next('span').text('');
}
// Remove any previously applied error or warning classes
$(field.container).parents('.form-group').removeClass('has-warning');
$(field.container).parents('.form-group').removeClass('has-success');
// Apply styling for a valid field
$(field.container).parents('.form-group').addClass('has-success');
} else if (field.isPotentiallyValid) {
// Remove styling from potentially valid fields
$(field.container).parents('.form-group').removeClass('has-warning');
$(field.container).parents('.form-group').removeClass('has-success');
if (event.emittedBy === 'number') {
$('#card-number').next('span').text('');
}
} else {
// Add styling to invalid fields
$(field.container).parents('.form-group').addClass('has-warning');
// Add helper text for an invalid card number
if (event.emittedBy === 'number') {
$('#card-number').next('span').text('Looks like this card number has an error.');
}
}
});
hostedFieldsInstance.on('cardTypeChange', function (event) {
// Handle a field's change, such as a change in validity or credit card type
if (event.cards.length === 1) {
$('#card-type').text(event.cards[0].niceType);
} else {
$('#card-type').text('Card');
}
});
$('.panel-body').submit(function (event) {
event.preventDefault();
hostedFieldsInstance.tokenize(function (err, payload) {
if (err) {
console.error(err);
return;
}
document.querySelector('input[name="payment_method_nonce"]').value = payload.nonce;
// This is where you would submit payload.nonce to your server
form.submit();
});
});
});
});
PHP
<?php
$result = Braintree_Transaction::sale([
'amount' => $_POST['amount'],
'orderId' => $_POST['order-number'],
'paymentMethodNonce' => $_POST['payment_method_nonce'],
'customer' => [
'firstName' => $_POST['first-name'],
'lastName' => $_POST['last-name'],
],
'options' => [
'submitForSettlement' => true
]
]);
if ($result->success === true){
}else
{
print_r($result->errors);
die();
}
?>
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact
support.
Remember when you collect form data on your server, you need to reference those inputs by their name attribute. Once you add the respective name values to each of these inputs, it should work as expected.
For example, your first name input;
<input type="text" placeholder="John" class="form-control" id="first-name" name="first-name">

Categories