Sending data in Laravel using Axios & Vue - javascript

I have been following the Stripe integration tutorial by Laracasts and it's become apparent to me that a lot has changed since Laravel 5.4 was released. I have been able to still find my way along but I have hit a bump trying to submit a payment form using Vue and Axios.
The product is being retrieved from a database and displayed in a select dropdown - this works. My issue is the data is not being properly sent to the store function in the PurchasesController. When I try to make a purchase the form modal appears fine, I fill it out with the appropriate test data and submit it, but in Chrome inspector I can see that /purchases returns a 404 error and when I check the network tab the error is: No query results for model [App\Product]
Here is the original Vue code:
<template>
<form action="/purchases" method="POST">
<input type="hidden" name="stripeToken" v-model="stripeToken">
<input type="hidden" name="stripeEmail" v-model="stripeEmail">
<select name="product" v-model="product">
<option v-for="product in products" :value="product.id">
{{ product.name }} — ${{ product.price /100 }}
</option>
</select>
<button type="submit" #click.prevent="buy">Buy Book</button>
</form>
</template>
<script>
export default {
props: ['products'],
data() {
return {
stripeEmail: '',
stripeToken: '',
product: 1
};
},
created(){
this.stripe = StripeCheckout.configure({
key: Laravel.stripeKey,
image: "https://stripe.com/img/documentation/checkout/marketplace.png",
locale: "auto",
token: function(token){
axios.post('/purchases', {
stripeToken: token.id,
stripeEmail: token.email
})
.then(function (response) {
alert('Complete! Thanks for your payment!');
})
.catch(function (error) {
console.log(error);
});
}
});
},
methods: {
buy(){
let product = this.findProductById(this.product);
this.stripe.open({
name: product.name,
description: product.description,
zipCode: true,
amount: product.price
});
},
findProductById(id){
return this.products.find(product => product.id == id);
}
}
}
</script>
And my PurchasesController.
<?php
namespace App\Http\Controllers;
use Log;
use App\Product;
use Illuminate\Http\Request;
use Stripe\{Charge, Customer};
class PurchasesController extends Controller
{
public function store()
{
Log::info("Product Info: " . request('product'));
Log::info("Stripe Email: " . request('stripeEmail'));
Log::info("Stripe Token: " . request('stripeToken'));
$product = Product::findOrFail(request('product'));
$customer = Customer::create([
'email' => request('stripeEmail'),
'source' => request('stripeToken')
]);
Charge::create([
'customer' => $customer->id,
'amount' => $product->price,
'currency' => 'aud'
]);
return 'All done';
}
}
I realise that product isn't being passed through to /purchases above so I have tried this:
axios.post('/purchases', {
stripeToken: token.id,
stripeEmail: token.email,
product: this.product
})
Unfortunately I still get the same No query results for model [App\Product] error even with that. Is there another/better way of passing data from Vue/Axios that I could use instead? If anyone is able to assist it would be much appreciated.
Thank you in advance.
Edit
The solution was to recast this to be a new variable and it started functioning again. Here is the relevant portion of the Vue Code that worked for me:
created(){
let module = this; // cast to separate variable
this.stripe = StripeCheckout.configure({
key: Laravel.stripeKey,
image: "https://stripe.com/img/documentation/checkout/marketplace.png",
locale: "auto",
token: function(token){
axios.post('/purchases', {
stripeToken: token.id,
stripeEmail: token.email,
product: module.product
})
.then(function (response) {
alert('Complete! Thanks for your payment!');
})
.catch(function (error) {
console.log(error);
});
}
});
},

Are you sure when assigning the product ID to data (using product: this.product) that this keyword is really your Vue instance? You might need to bind it manually calling .bind(this) on the .post(...) call.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

Related

Vue multiselect not adding new tags

I'm using a Vue Multiselect instance with 2 functions (one basically hits the database for an autocomplete function, which works. The other is adding a new one that isn't in the database)
So say 'Tag One' is in the database, if I type that and it shows then hitting enter or selecting will save it to the tags (multiselect with tagging enabled). However, if I type 'Tag Three' which isn't in the database and I hit enter or select, it just disappears and doesn't add to the tags or call the axios function in my addTag method.
What exactly am I doing wrong?
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<script src="https://unpkg.com/#johmun/vue-tags-input/dist/vue-tags-input.js"></script>
<div id="tagApp">
<multiselect
label="tag_data"
track-by="campaign_tag_id"
v-model="value"
:options="options"
:multiple="true"
:taggable="true"
#tag="addTag"
#search-change="val => read(val)"
:preselect-first="false"
:close-on-select="false"
:clear-on-select="true"
:preserve-search="true"
tag-placeholder="Add this as new tag"
placeholder="Search or add a tag"
></multiselect>
</div>
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
el: "#tagApp",
data() {
return{
value: [],
loading: false,
options: []
}
},
methods: {
read: function(val){
if (val) {
this.loading = true;
this.options = [];
const self = this;
console.log(val);
axios.get('search',{params: {query: val}})
.then(function (response) {
self.options = response.data;
console.log(response.data);
});
} else {
this.options = [];
}
},
addTag(newTag) {
const tag = {
tag_data: newTag,
};
const campaign_id = document.querySelector("input[name=campaign_id]").value;
this.options.push(tag);
this.value.push(tag);
axios.post('tags/save',{
tag_data: newTag,
})
.then(function (response){
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
}
})
I don’t think you are doing anything wrong, I just don’t think the component supports what you want to do.
But, what you could do is always add the search term to the options array if it isn’t there already. Putting the below in the axios callback could be all you need.
self.options = response.data;
self.options.push(val)
Now you could slice it to the front, or keep it pushed at the end, and code to prevent duplicates etc.

Can not delete post's comment reactively in Vue.js

In my blog-like Vue.js app, the posts are rendered in a v-for loop. When a button under each post is clicked, its comments are fetched from the server and attributed like this, as post objects have no comments attribute:
`this.$set(post, 'comments', res.data.comments) ;`
The code to render the comments is this:
<div v-for="(c, j) in post.comments" :key="j">
<span>{{c.body}}</span>
<span v-if="post.user_id==userId" #click="delComment(c, post)">Delete me!</span>
</div>
And the deleteing method:
delComment(c, post) {
const payload = {
token: this.token,
cid: c.id
}
axios.delete(this.BASE_URL + "/api/comment", { data: payload } )
.then( (res)=> {
post.comments = this.post.comments.filter((comment)=>{
return comment.id != c.id
});
this.$set(post, 'comments', post.comments) ;
})
.catch( error => {
console.log(error);
});
},
But when I click the Delete link, I get this error in the console:
TypeError: Cannot read property 'comments' of undefined
at eval (Home.vue?76f2:540)
How can I fix this?
You're missing this before post :
//v-------------//here
this.post.comments = this.post.comments.filter((comment)=>{
return comment.id != c.id
});
and here :
//----------v--------------------v
this.$set(this.post, 'comments', this.post.comments) ;
Update :
i was assuming that you a property called post in your data object as follows:
data(){
return{
post:[]
...
}
}
so in this case you should remove any this before post variable.
Alright, I realized that the culprit was an extra this in this.post.comments.filter((comment).
The working code is this:
delComment(c, post) {
const payload = {
token: this.token,
cid: c.id
}
axios.delete(this.BASE_URL + "/api/comment", { data: payload } )
.then( (res)=> {
post.comments = post.comments.filter((comment)=>{
return comment.id != c.id
});
this.$set(post, 'comments', post.comments) ;
})
.catch( error => {
console.log(error);
});
},
Thanks guys for sharing your thoughts!

Laravel delete method not working with vuejs

I am trying to complete a CRUD application with Vuejs and Laravel. Right now I can add an article but cannot delete and I ssee this error in my console:
SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON data
The html inside the template is like the following:
<div class="card card-body"v-for="article in articles" :key="article.id">
<h3>{{ article.title }}</h3>
<p>{{ article.body }}</p>
<button #click="deleteArticle(article.id)" class="btn btn-danger">Delete</button>
</div>
Then inside the script I have this:
How can I make the delete work?
<script>
export default {
data(){
return{
articles: [],
article: {
id: '',
title: '',
body: ''
},
article_id: '',
pagination: {},
edit: false
}
},
created(){
this.fetchAllArticles();
},
methods: {
fetchAllArticles(){
fetch('/api/articles').then(res => res.json()).then(res => {
this.articles = res.data;
})
.catch(err => console.log(err));
},
deleteArticle(id){
if(confirm('Are you sure?')){
fetch('api/article/${id}', {
method: 'delete'
})
.then(res => res.json())
.then(data => {
alert('Article removed');
this.fetchAllArticles();
})
.catch(err => console.log(err));
}
}
}
}
</script>
If I type my url for the delete method I can view the data as you can see in the image:
My delete controller looks like this:
public function destroy($id)
{
// Get article
$article = Article::findOrFail($id);
if($article->delete()) {
return new ArticleResource($article);
}
}
From the netwok in the browser I see this:
Thanks in advance
I have never use fetch i love using axios, but through googling i found that you have to use not those symbols '' but those `` so:
fetch(`api/article/${id}`, {
method: 'delete'
})
From what I see in your console the API address is incorrect, should be 'api/article/' + id

Keeping track of added element in an array?

I'm playing around with vue.js and the .vue components, and as newbie, I'm wondering how can I keep track of the element I add in the array.
The situation is the following :
The user add a new element from a form
When he submit, the data are automatically added to a ul>li element, and a POST request is made to the API
When the POST is done, I want to update the specific li with the new data from the server.
The thing is, I can not target the last li because the server can take time to process the request (he do a lot of work), so the user may have added 1, 5, 10 other entries in the meantime.
So how can I do ?
Here's my code so far :
<template>
<form method="post" v-on:submit.prevent="search">
<input type="text" placeholder="Person name" required v-model="name" v-el="nameInput" />
<input type="text" placeholder="Company" required v-model="company" v-el="domainInput" />
<input type="submit" value="Search" class="btn show-m" />
</form>
<ul>
<li transition="expand" v-for="contact in contacts">
<img v-bind:src="contact.avatar_url" width="40px" height="40px" class="cl-avatar" />
<div class="cl-user">
<strong class="cl-name">{{contact.name}} <span class="cl-company">{{contact.company}}</span></strong>
</div>
</li>
</ul>
</template>
<script>
export default {
data () {
return {
contacts: [],
name: null,
company: null
}
},
methods: {
search: function (event) {
this.$http.post('/search', {
name: this.name,
company: this.company
}).then(function (xhr) {
// HERE ! How can I target the exact entry ?
this.contacts.unshift(xhr.data)
})
this.name = ''
this.company = ''
this.contacts.unshift({'name': this.name, 'company': this.company})
},
}
}
</script>
Thank you for your help ! :)
If you know that the name and company fields are unique you could search through the array to find it... otherwise you can just wait to append it to the array until the return function:
search: function (event) {
this.$http.post('/search', {
name: this.name,
company: this.company
}).then(function (xhr) {
this.contacts.unshift(xhr.data)
})
this.name = ''
this.company = ''
},
I finally found a working solution : I use a component instead of <li /> for each entries, and manage the state of these inside the component :
<ul>
<contact-entry v-for="contact in contacts" v-bind:contact="contact"></contact-entry>
</ul>
That way, when I add a new entry in the array (described above), a new instance of the component contact-entry is made.
Inside that component, I did the following :
<script>
export default {
props: ['contact'],
created: function () {
if (this.contact.id === undefined) { // If it's a new contact
this.$http.post('search/name', { // I do the post here
name: this.contact.name,
domain: this.contact.company.name
}).then(function (xhr) {
this.contact = xhr.data // This will update the data once the server has replied, without hassle to find the correct line
})
}
}
}
</script>
That's it ! :) In the parent's component, I removed the xhr request and simplified the method to :
<script>
export default {
data () {
return {
contacts: [],
name: null,
company: null
}
},
methods: {
search: function (event) {
this.name = ''
this.company = ''
this.contacts.unshift({'name': this.name, 'company': this.company})
}
}
}
</script>

How to charge a stripe card in meteor

Having a warm time trying to charge a card in Meteor. The error I get is: Exception while invoking method 'chargeCard' Error: Match error: Expected string, got object. I do get the modal where I typed in the email and card number but after pressing the pay button, in terminal I get the error message.
How to call the charge function properly? I cant find any tutorial that matches closely the way I implement it.
The setup is very basic. I also have jquery installed.
Template:
<template name="hello">
<form id="myForm">
<input type="text" id="amount" name="amount"/>
<input type="hidden" id="stripeToken" name="stripeToken"/>
<input type="hidden" id="stripeEmail" name="stripeEmail"/>
</form>
<hr>
<button id="customButton">Pay</button>
</template>
js:
if (Meteor.isClient) {
Template.hello.helpers({
});
Template.hello.events({
'click button': function (e) {
e.preventDefault();
var handler = StripeCheckout.configure({
key: 'pk_test_rand',
token: function(token) {
$("#stripeToken").val(token.id);
$("#stripeEmail").val(token.email);
$("#myForm").submit();
Meteor.call('chargeCard', token); // this seem not right?
}
});
// Showing the pop up Stripe dialog
var amount = $("#amount").val() *100;
// Open Checkout with further options
handler.open({
name: 'Demo Site',
description: '2 widgets ($20.00)',
amount: amount
});
// Close Checkout on page navigation
$(window).on('popstate', function() {
handler.close();
});
}
});
Meteor.startup(function(){
$.getScript('https://checkout.stripe.com/checkout.js', function(){
// script has loaded
});
});
}
if (Meteor.isServer) {
Meteor.methods({
'chargeCard': function(stripeToken) {
check(stripeToken, String);
var Stripe = StripeAPI('sk_test_rand');
Stripe.charges.create({
source: stripeToken,
amount: 5000, // this is equivalent to $50
currency: 'usd'
}, function(err, charge) {
console.log(err, charge);
});
}
});
}
It seems you're passing the whole token object:
Meteor.call('chargeCard', token);
But your chargeCard() method expects a string:
check(stripeToken, String);
So you need to either pass only the token id:
Meteor.call('chargeCard', token.id);
or change your chargeCard() method to expect and use the whole token object:
Meteor.methods({
'chargeCard': function(stripeToken) {
check(stripeToken, Object);
var Stripe = StripeAPI('sk_test_rand');
Stripe.charges.create({
source: stripeToken.id,
...

Categories

Resources