How to clear elements from Stripe elements in Angular2 - javascript

I have form in modal, which include elements from Stripe. App is single app in Angular2. Problem is when user click on modal, type something without submitting form and close modal, on next opening modal is populated with previous data. It seems that I cannot change values of Stripe elements (for credit card number, cvv, postal code, exp date). I didn't manage to find any documented method for this from Stripe.js. I tried to change values of elements, Stripe blocks me. I tried to unmount and mount element again (in order to destroy and create it again), Stripe gives me multiple errors. Does anyone know how to reset Stripe elements in form so that form is clear on opening modal?

I think you should use element.clear()
$stripe_card = elements.create('card', {style: style});
$stripe_card.mount(..);
...
$stripe_card.clear();
Stripe Element Docs

simply unmount it(stripe element) first then mount.
Html part
<form class="stripe_payment_form_div" ng-submit="saveCard()">
<div class="stripe_card_element_div">
<label for="stripe-card-element"></label>
<div id="stripe-card-element" class="field"></div>
</div>
<div class="stripe_payment_completion_div_wrapper">
<div class="stripe_payment_completion_div">
<span class="stripe_card_save_btn_div">
<button class="md-btn md-btn-primary" type="submit">Save Card</button>
</span>
<span class="stripe_card_cancel_btn_div">
<a class="uk-text uk-text-center uk-text-middle" id="cancel_payment_method_btn">Cancel</a>
</span>
</div>
</div>
</form>
Controller part
$scope.stripe = Stripe('#your stripe key');
$scope.stripeCard = null;
var stripeCardElementMoutingId = "#stripe-card-element";
function buildStripeCardElement() {
if (null == $scope.stripe) {
$scope.stripe = Stripe('#your stripe key');
}
var stripeElements = $scope.stripe.elements();
var stripeCard = stripeElements.create('card', {
style: {
base: {
iconColor: '#9cabbc',
color: '#31325F',
lineHeight: '40px',
fontWeight: 300,
fontFamily: 'Helvetica Neue',
fontSize: '13px',
fontSmoothing: 'antialiased',
'::placeholder': {
color: '#9cabbc',
},
},
}, hidePostalCode: true,
iconStyle: 'solid'
});
return stripeCard;
}
var loadStripeElement = function () {
//----- Load Stripe Card Element Div -----//
$scope.stripeCard = buildStripeCardElement();
$scope.stripeCard.mount(stripeCardElementMoutingId);
$scope.stripeCard.on('change', function (event) {
//----- Handle error messages in case of invalid input -----//
stripeCreateTokenResponseHandler(event);
});
};
$("#cancel_payment_method_btn").click(function () {
//----- Below statements can be merge into a single function -----//
$scope.stripeCard.unmount();
$scope.stripeCard = buildStripeCardElement();
$scope.stripeCard.mount(stripeCardElementMoutingId);
$scope.stripeCard.on('change', function (event) {
stripeCreateTokenResponseHandler(event);
});
});
//------------- Load stripe element ------------------//
loadStripeElement();

Related

Stripe payment always succeeds and the amount of the paymentIntent cannot be changed

I am struggling for a long period of time to implement Stripe in my Laravel app, but I've encountered a lot of problems. I did somehow implemented the logic but I cannot or I don't know how I can send the current order total to create the paymentIntent, all of the payments are stored with a default amount of 500, set up in the Controller. I have to mention that after the success response from stripe, the current order should be stored in the database with user shipping details which are found in the first form and all the products ordered, stored in session. Let me show you for a better understanding.
This is the view (revieworder.blade.php) where I have a 2 forms, one with the user's shipping details, and the Stripe payment form, and a list of cart products from the session:
<ul class="list-group mb-3">
<?php $total = 0 ?>
#if(session('cart'))
#foreach(session('cart') as $id => $details)
<?php $total += $details['price'] * $details['quantity'] ?>
<li class="list-group-item d-flex justify-content-between lh-condensed">
<img src="../img/{{ $details['image'] }}" alt="{{ $details['name'] }}" width="60" height="60">
<div>
<h6 class="my-0">{{ $details['name'] }}</h6>
<small class="text-muted">{{ __('Quantity') }}: {{ $details['quantity'] }}</small><br>
<small class="text-muted">{{ __('Unit price') }}: {{ $details['price'] }} RON</small>
</div>
<span class="text-muted">{{ $details['price'] * $details['quantity'] }} RON</span>
</li>
#endforeach
#endif
<li class="list-group-item d-flex justify-content-between">
<span>Total (RON)</span>
<strong id="total">{{ $total.' RON' }}</strong>
</li>
</ul>
<form id="payment-form">
#csrf
<div id="card-element"><!--Stripe.js injects the Card Element--></div>
<button id="submit" class="submit-id">
<div class="spinner hidden" id="spinner"></div>
<span id="button-text">Pay now</span>
</button>
<p id="card-error" role="alert"></p>
<p class="result-message hidden">
</p>
</form>
<script>
//Stripe script
var stripe = Stripe("pk_test_XXX");
// The items the customer wants to buy
var purchase = {
items: [{id: "prod"}] //sessions cart
};
console.log(purchase);
var elements = stripe.elements();
var style = {
base: { //some styling },
invalid: {
fontFamily: 'Arial, sans-serif',
color: "#fa755a"
}
};
var card = elements.create("card", { style: style });
// Stripe injects an iframe into the DOM
card.mount("#card-element");
card.on("change", function (event) {
// Disable the Pay button if there are no card details in the Element
document.querySelector("button").disabled = event.empty;
document.querySelector("#card-error").textContent = event.error ? event.error.message : "";
});
// Disable the button until we have Stripe set up on the page
document.getElementsByClassName("submit-id").disabled = true;
$('#payment-form').submit(function(){
fetch("{{ url(app()->getLocale().'/revieworder') }}", {
method: "POST",
headers: {
"Content-Type": "application/json",
'X-CSRF-TOKEN': "{{ csrf_token() }}"
},
body: JSON.stringify(purchase)
})
.then(function(data) {
$('#payment-form').submit(function(event) {
event.preventDefault();
// Complete payment when the submit button is clicked
payWithCard(stripe, card, data.clientSecret);
});
});
// Calls stripe.confirmCardPayment
var payWithCard = function(stripe, card, clientSecret) {
loading(true);
stripe
.confirmCardPayment(clientSecret, {
payment_method: {
card: card
}
})
.then(function(result) {
if (result.error) {
// Show error to your customer
showError(result.error.message);
} else {
// The payment succeeded!
// The order should be stored in the database
orderComplete(result.paymentIntent.id);
}
});
};
// Shows a success message when the payment is complete
var orderComplete = function(paymentIntentId) {
loading(false);
document
.querySelector(".result-message a")
.setAttribute(
"href",
"https://dashboard.stripe.com/test/payments/" + paymentIntentId
);
document.querySelector(".result-message").classList.remove("hidden");
document.getElementsByClassName("submit-id").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.getElementsByClassName("submit-id").disabled = true;
document.querySelector("#spinner").classList.remove("hidden");
document.querySelector("#button-text").classList.add("hidden");
} else {
document.getElementsByClassName("submit-id").disabled = false;
document.querySelector("#spinner").classList.add("hidden");
document.querySelector("#button-text").classList.remove("hidden");
}
};
});
</script>
And then this is the controller which handles the secret client key and the paymentIntent (CheckoutController.php):
public function create(){
\Stripe\Stripe::setApiKey('sk_test_XXX');
header('Content-Type: application/json');
try {
$json_str = file_get_contents('php://input');
$json_obj = json_decode($json_str);
$paymentIntent = \Stripe\PaymentIntent::create([
'amount' => "500",
'currency' => 'ron',
]);
$output = [
'clientSecret' => $paymentIntent->client_secret,
];
echo json_encode($output);
} catch (Error $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
}
So, one of the problems is that whenever I get to pay for an order, no matter what credit card number I pass, it always succeeds, and that is a major bug. The second one, I need to pass the total of the current order, otherwise all orders will get a total amount of 500, the default value. I tried to pass the session cart items to the fetch but it didn't work. Even though I cannot send all the items from the cart to the intent, at least the total price should correspond.
If I wasn't clear enough with anything, please let me know. I would really appreciate any piece of advice. Thank you !
**Edit:
You can update the PaymentIntent amount via the API (reference) before you confirm it (either on the server or client side. If you're encountering errors sending this, please share the details of that error.
You need to say more about the "it always succeeds" and exactly what request you are making. You did not show how you are using Stripe.js. You should use the test cards that Stripe provides to test success and failure cases. If you are getting different results than specified you will need to provide more detail about what exactly you're doing and what you expect the result to be.

How To Display My Invoice Data In Invoice Template

I'm using Laravel 5.7 & VueJs 2.5.* ...
I have invoices table, i need to display specific invoice in a new component, so user can see whatever invoice he wants or print that invoice.
I don't know how to do that, i'm just playing around, if you could help me out, i'll be very grateful to you.
<router-link> to the component
<router-link to="/ct-invoice-view" #click="openInvoice(ctInvoice)">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Displaying Customer information here like this:
<div class="col-sm-4 invoice-col">
<address v-for="ctInvoice in ctInvoices" :key="ctInvoice.id">
<strong>Customer Info</strong><br>
Name: <span>{{ ctInvoice.customer.customer_name }}</span>
Invoice view component data() & method{}
data() {
return {
ctInvoices: {},
customers: null
};
},
methods: {
openInvoice(ctInvoice) {
axios
.get("api/ct-invoice/show/" + this.viewInvoice)
.then(({
data
}) => (this.ctInvoices = data.data));
},
Image for Better Understanding
You need to look at Dynamic Route matching: https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes
Then you need to use axios.get in invoice views beforeMount function where this.$route.params.id will hold the invoice ID you want to load if the link is applied like so:
<router-link :to="`/ct-invoice-view/${ctInvoice.id}`">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Alternatively...
I suggest not navigating away from the list, it can be irritating for users having filtered the list then returning to it to look at more invoices and having to filter again unless the filter options and current results are sticky
There are a number of ways of doing this and they are lengthy to example, Typically I would make proper use of a modal and the invoice view load the data on display but to get you started a basic in page solution to experiment with, then try adapting in a reusable modal component later:
<button #click="showInvoice = ctInvoice.id">
<i class="fas fa-eye fa-lg text-blue"></i>
</button>
data() {
return {
loading: false,
invoice: {},
customers: null
};
},
computed: {
showInvoice: {
get: function() {
return this.invoice.hasOwnProperty('id');
},
set: function(value) {
if(value === false) {
this.invoice = {};
return;
}
// could check a cache first and push the cached item into this.invoice else load it:
this.loading = true;
axios.get("api/ct-invoice/show/" + value).then(response => {
// you could push the invoice into a cache
this.invoice = response.data;
}).cache(error => {
// handle error
}).finally(() => {
this.loading = false;
});
}
}
}
In view-invoice component have a close button with bind #click="$emit('close')"
Check this article for how $emit works: https://v2.vuejs.org/v2/guide/components-custom-events.html
<div v-if="loading" class="loading-overlay"></div>
<view-invoice v-if="showInvoice" :invoice="invoice" #close="showInvoice = false" />
<table v-else>....</table>
Hide the table when displaying the invoice, experiment with using v-show instead of v-if upon loosing table content state.
Inside your invoice view, property called invoice will contain the invoice data.
Check this article for how to use props: https://v2.vuejs.org/v2/guide/components-props.html
Hint: The #close listens to the $emit('close')
Could also make use of when switching between table and invoice view.
https://v2.vuejs.org/v2/guide/transitions.html
#MarcNewton
I did something like this, it's working for me, can u just review it for me:
<router-link> to the Invoice View component
<router-link v-bind:to="{name: 'ctInvoiceView', params: {id: ctInvoice.id}}">
<i class="fas fa-eye fa-lg text-blue"></i>
</router-link>
Getting Data of Specific Invoice ID Like This:
created: function() {
axios
.get("/api/ct-invoice/" + this.$route.params.id)
.then(({
data
}) => {
console.log(data);
this.form = new Form(data);
})
.catch(error => {
console.log(error.response);
});
},

Charging a custom amount with Stripe and Node.js

I'm going to try and be thorough as possible. So what I'm trying to do is charge a user a percentage of the overall total that is calculated. How do we get the total? Well, the total cost is depended upon the progression of questions the user answers.
If the user needs a specific service then the cost might increase a bit, say to $100, but we're wanting to charge just 10% of that total that constantly changes like stated before. Currently, the amount is hardcoded, but since the amount changes depending on their services select, I can't have it hard coded.
I'm new to Stripe and Node but is there an easy way such as 'document.getElementById'? of something similar? The charge and everything work correctly but I believe I'm getting confused on the routing.
My HTML form (with the area where the total will be displayed):
<div class="" style="margin-top: 60px;">
<h2 class="quote-info">Estimated total: $<span id="new_text"></span> USD</h2>
<h2 class="quote-info">Reservation deposit: $<span id="new_text2"></span> USD</h2>
</div>
<!-- Payment form -->
<form action="/charge" method="post" id="payment-form">
<div class="form-row">
<label for="card-element">
Credit or debit card
</label>
<div id="card-element">
<!-- a Stripe Element will be inserted here. -->
</div>
<!-- Used to display form errors -->
<div id="card-errors"></div>
</div>
<input type="hidden" name="totalAmount" value="1000">
<button>Submit Payment</button>
</form>
My script tag that's found at the bottom of my HTML file that contains the form above:
<script type="text/javascript">
//Create a Stripe client
var stripe = Stripe('my_key_should_go_here');
// Create an instance of Elements
var elements = stripe.elements();
// Custom styling can be passed to options when creating an Element.
// (Note that this demo uses a wider set of styles than the guide below.)
var style = {
base: {
color: '#32325d',
lineHeight: '24px',
fontFamily: '"Helvetica Neue", Helvetica, sans-serif',
fontSmoothing: 'antialiased',
fontSize: '16px',
'::placeholder': {
color: '#aab7c4'
}
},
invalid: {
color: '#fa755a',
iconColor: '#fa755a'
}
};
// Create an instance of the card Element
var card = elements.create('card', {style: style});
// Add an instance of the card Element into the `card-element` <div>
card.mount('#card-element');
// Handle real-time validation errors from the card Element.
card.addEventListener('change', function(event) {
var displayError = document.getElementById('card-errors');
if (event.error) {
displayError.textContent = event.error.message;
} else {
displayError.textContent = '';
}
});
// Handle form submission
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();
stripe.createToken(card).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;
} else {
// Send the token to your server
stripeTokenHandler(result.token);
}
});
});
function stripeTokenHandler(token) {
// Insert the token ID into the form so it gets submitted to the server
var form = document.getElementById('payment-form');
var hiddenInput = document.createElement('input');
hiddenInput.setAttribute('type', 'hidden');
hiddenInput.setAttribute('name', 'stripeToken');
hiddenInput.setAttribute('value', token.id);
form.appendChild(hiddenInput);
var formData = JSON.stringify({
mytoken: token.id
});
$.ajax({
type: "POST",
url: "/charge",
data: formData,
success: function(){alert("done")},
dataType: "json",
contentType: "application/json"
});
// alert("Created a token with value: "+token.id)
form.submit();
}
</script>
And lastly, my app.js file:
const express = require('express');
const stripe = require('stripe')('deleted_key');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
// Set Static Folder
app.use(express.static('public'));
// Index route
app.get('/charge', (req, res) => {
res.send();
});
// charge route
app.post('/charge', (req, res) => {
// const amount = 2500;
const amount = req.body.totalAmount;
stripe.customers.create({
email: "random#gmail.com",
source: req.body.mytoken
})
.then(customer => {
stripe.charges.create({
amount,
description:'specified service description here',
currency:'usd',
customer:customer.id
})})
.then(charge => res.send('success'));
});
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
My primary question is this, how would I go about obtaining the amount given in the 'new_text' area in my HTML file to use and charge the user in node?
To use the getElementById is to add an ID to your amount field first:
<input type="hidden" name="totalAmount" id="totalAmount" value="1000">
Then you can use the ID to get the value of the field:
const amount = document.getElementById("totalAmount").value;
Although, I can see that your input type is hidden - is that intentional?

Braintree JSv3 payment_method_nonce Value Bad With HostedFields

I have looked at a few posts on here with the same issue but under different circumstances that don't supply me with an answer to my particular issue...
I was using Braintree JSv2 with my Django project and all was working fine. Since I have migrated over to v3 of Braintree, the only issue I seem to have right now is that the value inputted to "payment_method_nonce" is not there...
Here is the code that is supposed to be dumping the payment_method_nonce value:
document.querySelector('input[name="payment_method_nonce"]').value = payload.nonce;
And here is the code that is supposed to be grabbing it on the python side:
client_payment_nonce = request.POST['payment_method_nonce']
When submitting this in my dev environment, I get an error (MultiValueDictKeyError) for "payment_method_nonce".
I am using Django 1.9 and Python 2.7. I am also using the example given by Braintree for a simple integration using HostedFields...
Small test
So I manually added an input field in my form with name "payment_method_nonce" just to see if not having a field was causing some issue. I know it is injected by Braintree but just testing a thought. It seems that although the value of payment_method_nonce is supposed to be my nonce, I didn't type anything into the input box and it was still coming back as null.
Full Snippets of Form and HostedFields
<form action="/booking/" method="post" id="checkout_form">
{% csrf_token %}
<div class="payment">
<span>Payment</span>
<!--input elements for user card data-->
<div class="hosted-fields" id="card-number"></div>
<div class="hosted-fields" id="postal-code"></div>
<div class="hosted-fields" id="expiration-date"></div>
<div class="hosted-fields" id="cvv"></div>
<div class="btns">
<input type="hidden" name="payment_method_nonce">
<input type="submit" value="Complete Booking" id="pay-button">
</div>
</div>
</form>
Note: I had just changed the payment_method_nonce field to type="hidden" instead of type="text" but still have the same effect...
<!-- load the required client component -->
<script src="https://js.braintreegateway.com/web/3.15.0/js/client.min.js"></script>
<!-- load the hosted fields component -->
<script src="https://js.braintreegateway.com/web/3.15.0/js/hosted-fields.min.js"></script>
<!-- Braintree setup -->
<script>
var client_token = "{{ request.session.braintree_client_token }}"
var form = document.querySelector('#checkout-form');
var submit = document.querySelector('input[type="submit"]');
braintree.client.create({
authorization: client_token
}, function (clientErr, clientInstance) {
if (clientErr) {
// Handle error in client creation
return;
}
braintree.hostedFields.create({
client: clientInstance,
styles: {
'input': {
'font-size': '14px'
},
'input.invalid': {
'color': 'red'
},
'input.valid': {
'color': 'green'
}
},
fields: {
number: {
selector: '#card-number',
placeholder: 'Credit Card Number'
},
cvv: {
selector: '#cvv',
placeholder: '123'
},
expirationDate: {
selector: '#expiration-date',
placeholder: '10/2019'
},
postalCode: {
selector: '#postal-code',
placeholder: '10014'
}
}
}, function (hostedFieldsErr, hostedFieldsInstance) {
if (hostedFieldsErr) {
// handle error in Hosted Fields creation
return;
}
submit.removeAttribute('disabled');
form.addEventListener('submit', function (event) {
event.preventDefault();
hostedFieldsInstance.tokenize(function (tokenizeErr, payload) {
if (tokenizeErr) {
// handle error in Hosted Fields tokenization
return;
}
// Put `payload.nonce` into the `payment_method_nonce`
document.querySelector('input[name="payment_method_nonce"]').value = payload.nonce;
document.querySelector('input[id="pay-button"]').value = "Please wait...";
form.submit();
});
}, false);
});
});
</script>
Note: the line document.querySelector('input[id="pay-button"]').value = "Please wait..."; doesn't fire (I know this because the button does not change values). Maybe these querySelector lines just aren't working?
Something New Noticed
I just went back to my page and hit the submit button without even entering any information. In v2 of Braintree, I would not be able to click the submit button until all fields were filled in... Maybe the values in my form aren't even being sent to braintree to receive a nonce and that's why there is an empty string being returned..?
Moral of the story
Review your code... Multiple times. As pointed out by C Joseph, I have my form ID as something different than what my var form is referencing...
<form action="/booking/" method="post" id="checkout_form">
var form = document.querySelector('#checkout-form');

Braeintree Client Set Up with JS v3 - payment_method_nonce null and the form isn't submitting

I've tried to integrate Braintree in my Laravel 5.2 app and everything works fine with JS v2 client setup, but I would like to upgrade it to v3.
This is from the docs (I've customized a bit):
<form id="checkout-form" action="/checkout" method="post">
<div id="error-message"></div>
<label for="card-number">Card Number</label>
<div class="hosted-field" id="card-number"></div>
<label for="cvv">CVV</label>
<div class="hosted-field" id="cvv"></div>
<label for="expiration-date">Expiration Date</label>
<div class="hosted-field" id="expiration-date"></div>
<input type="hidden" name="payment-method-nonce">
<input type="submit" value="Pay">
</form>
<!-- Load the Client component. -->
<script src="https://js.braintreegateway.com/web/3.0.0-beta.8/js/client.min.js"></script>
<!-- Load the Hosted Fields component. -->
<script src="https://js.braintreegateway.com/web/3.0.0-beta.8/js/hosted-fields.min.js"></script>
<script>
var authorization = '{{ $clientToken }}'
braintree.client.create({
authorization: authorization
}, function (clientErr, clientInstance) {
if (clientErr) {
// Handle error in client creation
return;
}
braintree.hostedFields.create({
client: clientInstance,
styles: {
'input': {
'font-size': '14pt'
},
'input.invalid': {
'color': 'red'
},
'input.valid': {
'color': 'green'
}
},
fields: {
number: {
selector: '#card-number',
placeholder: '4111 1111 1111 1111'
},
cvv: {
selector: '#cvv',
placeholder: '123'
},
expirationDate: {
selector: '#expiration-date',
placeholder: '10 / 2019'
}
}
}, function (hostedFieldsErr, hostedFieldsInstance) {
if (hostedFieldsErr) {
// Handle error in Hosted Fields creation
return;
}
form.addEventListener('submit', function (event) {
event.preventDefault();
hostedFieldsInstance.tokenize(function (tokenizeErr, payload) {
if (tokenizeErr) {
// Handle error in Hosted Fields tokenization
return;
}
document.querySelector('input[name="payment-method-nonce"]').value = payload.nonce;
form.submit();
});
}, false);
});
});
</script>
But when I click the submit button, nothing happens.event.preventDefault() stops the submission and the payment_method_nonce token is generated, but I can't submit the form after that, because form.submit() isn't works
How can I submit the form after event.preventDefault()?
Or how can I send the payment_method_nonce token to my controller?
Thanks!
When I copied your snippet and tried to run the example, I got (index):69 Uncaught ReferenceError: form is not defined in the console. When I added
var form = document.getElementById('checkout-form');
it worked just fine.
Best guess, you just forgot to assign the form variable to reference the form dom element. If that's not the case, be sure to let me know.

Categories

Resources