I am using the Yelp API (by making requests to it using https://cors-anywhere.herokuapp.com/ as a proxy—because the Yelp API itself isn’t CORS-enabled) to attempt to create an app very similar to a Yelp search for my own practice. I am able to get the response into my browser's console. The response has an array named businesses with the businesses that match the search. I am trying to use .(map) to map through the businesses array. However, I keep getting Cannot read property 'map' of undefined.
The reponse I receive from the Yelp API is below. Thanks
Yelp API Response Image
Also if there is any other tips about javascript that come to mind when looking at my code please share as I am very early into my programming career.
const Yelp = {
getAccessToken: function() {
if(accessToken) {
return new Promise(resolve => resolve(accessToken));
};
return fetch(accessTokenUrl, {
method: 'POST'
}).then(response => response.json()).then(jsonResponse => {
accessToken = jsonResponse.access_token;
})
},
search: function(term,location,sortBy) {
return this.getAccessToken().then(() => {
const yelpRetrieveUrl = `${corsAnywhereUrl}https://api.yelp.com/v3/businesses/search?term=${term}&location=${location}&sort_by=${sortBy}`;
return fetch(yelpRetrieveUrl, {
headers: {Authorization: `Bearer ${accessToken}`}
});
}).then(jsonResponse => {
if(1 > 0){
console.log('true!!!!');
return jsonResponse.businesses.map(business => {
return {
id: business.id,
imageSrc: business.image_url,
name: business.name,
address: business.location.address1,
city: business.location.city,
state: business.location.state,
zipCode: business.location.zip_code,
category: business.categories,
rating: business.rating,
reviewCount: business.review_count
};
});
} else {
console.log('FALSE')
}
})
}
};
fetch(myRequest).then(function(response) {
var contentType = response.headers.get("content-type");
if(contentType && contentType.includes("application/json")) {
return response.json();
}
throw new TypeError("Oops, we haven't got JSON!");
})
.then(function(json) { /* process your JSON further */ })
.catch(function(error) { console.log(error); });
You might search for the jsonResponse#json function to turn your json dataset into a real object that you can process in JavaScript.
And because you asked for it: As you are using Promises, make use of the Promise#Catch function to handle upcoming errors. Don't let the browser handle them, because they can have different behaviors in different browsers.
And probably remove the 1 > 0 check, because it will always be true, but I think this was only for test purposes.
I hope I could help you! I might append the code later since I'm currently on mobile.
Related
I want to upgrade my simple Stripe one-page checkout to use the new Payment Element that unifies a lot of different payment methods under one component. By simple I mean, the customer chooses between a few variants of one product, provides needed info and submits the order. Collect money, send emails, fulfil the order etc. Just vanilla HTML/CSS/JS and a bit of PHP. Using Payment Intents API to process the payments, was on Charges API before.
I love the premise of this unified element so I decided to give it a go. It turns out I have trouble understanding what to do with both stripe.confirmPayment method and return_url parameter.
I guess the return_url should be my checkout page? Also, is there a way to redirect without hard refresh? Ideally, I would be able to do some server-side stuff before redirect happens, but it seems that stripe.confirmPayment automatically redirects if resolved successfully.
Here is my code. I am a designer btw, so guess I am missing something obvious as always.
// init Stripe elements
fetch('/config.php', {
method: 'get',
headers: {
'Content-Type': 'application/json'
}
})
.then((response) => {
return response.json();
})
.then((response) => {
return setupElements(response.publishableKey)
})
var setupElements = function (publishableKey) {
stripe = Stripe(publishableKey);
// create payment intent to setup payment element
fetch('/setup-elements.php', {
method: 'POST',
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(order)
})
.then(function (response) {
return response.json()
})
.then(function (data) {
const appearance = {
theme: 'none',
labels: 'floating',
// etc.....
};
elements = stripe.elements({
clientSecret: data.clientSecret,
fonts: [{
cssSrc: 'https://use.typekit.net/hly2qug.css'
}, ],
appearance
});
const paymentElement = elements.create("payment", {
fields: {
billingDetails: {
email: 'never',
address: {
line1: 'never',
city: 'never',
state: 'never',
country: 'never',
postalCode: 'never'
}
}
}
});
paymentElement.mount("#payment-element");
})
}
form.addEventListener('submit', function (e) {
e.preventDefault();
var isFormValid = validate.validateAll(form);
if (isFormValid.length < 1) {
loading(true);
collectFormInfo();
confirmPayment();
}
})
var confirmPayment = function () {
stripe.confirmPayment({
elements,
confirmParams: {
return_url: 'checkout-page?',
payment_method_data: {
billing_details: {
email: order.customer.email,
address: {
line1: order.delivery.address,
city: order.delivery.city,
state: order.delivery.state,
country: order.delivery.country,
postal_code: order.delivery.postcode
}
}
}
}
})
.then(function (result) {
// This is where I get stuck. How to do stuff after
// payment is confirmed
// and not get redirected immediately? If
//redirected, where to and is it
// possible to do so asynchronously?
if (result.error.type === "card_error" || result.error.type === "validation_error") {
showMessage(result.error.message);
} else {
// get client secret
const clientSecret = new URLSearchParams(window.location.search).get(
"payment_intent_client_secret"
);
// bail if no client secret
if (!clientSecret) {
return;
} else {
stripe.retrievePaymentIntent(clientSecret).then(function (response) {
switch (response.paymentIntent.status) {
case "succeeded":
showMessage("Payment succeeded!");
break;
case "processing":
showMessage("Your payment is processing.");
break;
case "requires_payment_method":
showMessage("Payment failed. Please try another payment method.");
break;
default:
showMessage("Something went wrong.");
break;
}
});
}
}
})
}
Nudge in the right direction is all I need, at least I hope so
Yes the return_url should be your own page that Stripe will automatically redirect after your customer completed the Payment: Stripe Doc. 'checkout-page?' doesn't look like a valid URL. Normally you would want something like 'http://localhost:4242/success'
This action is done automatically on client and you can't intercept it. Any action you want to perform on server should be handled via webhook, at Step 6 "Handle post-payment events" at the same article above.
I am using mithril.js in frontend application and backend application is running on ipv6 environment.
Calling post ajax request to backend using mithril.js.
async post(url, body = {}) {
return new Promise((resolve, reject) => {
m.request({method: 'POST', url, body}).then((data) => {
resolve(data);
}).catch((err) => {
reject(err.message);
});
});
}
Backed url is like this: http://[340f:c0e0:1d1:5gc0:g4fs:2::]:22923/backend.
But getting this error Template parameter names *must* be separated while calling backend api.
Explanation of the error
Based on the documentation of m.request(), you can specify dynamic URLs:
Request URLs may contain interpolations:
m.request({
method: "GET",
url: "/api/v1/users/:id",
params: {id: 123},
}).then(function(user) {
console.log(user.id) // logs 123
})
In the code above, :id is populated with the data from the params object, and the request becomes GET /api/v1/users/123.
Interpolations are ignored if no matching data exists in the params property.
m.request({
method: "GET",
url: "/api/v1/users/foo:bar",
params: {id: 123},
})
In the code above, the request becomes GET /api/v1/users/foo:bar?id=123
Since your backend URL contains colons, it's interpreted as being a dynamic URL.
According to the documentation of m.buildPathname(), m.request() uses m.buildPathname() internally to process dynamic URLs.
The beginning of m.buildPathname() contains the following check regarding parameters of a path template (dynamic URL = path template populated with path parameters):
if ((/:([^\/\.-]+)(\.{3})?:/).test(template)) {
throw new SyntaxError("Template parameter names *must* be separated")
}
(Source: https://github.com/MithrilJS/mithril.js/blob/v2.0.4/mithril.js#L1288-L1292)
And, again, since your backend URL contains colons, this is where you are getting the error. (You can verify this by trying to run m.buildPathname('http://[340f:c0e0:1d1:5gc0:g4fs:2::]:22923/backend') – you'll get the same error.)
How to fix it
Since you can't get away from that regex check at the beginning of m.buildPathname(), your best bet might be to use a dynamic URL. Like so:
m.buildPathname(':url...', { url: 'http://[340f:c0e0:1d1:5gc0:g4fs:2::]:22923/backend' })
// => http://[340f:c0e0:1d1:5gc0:g4fs:2::]:22923/backend
Or when applied to your code:
async post(url, body = {}) {
return new Promise((resolve, reject) => {
m.request({method: 'POST', url: ':url...', body, params: {url}}).then((data) => {
resolve(data);
}).catch((err) => {
reject(err.message);
});
});
}
Or alternatively you can specify the (dynamic) URL as the first argument of m.request():
async post(url, body = {}) {
return new Promise((resolve, reject) => {
m.request(':url...', {method: 'POST', body, params: {url}}).then((data) => {
resolve(data);
}).catch((err) => {
reject(err.message);
});
});
}
Notice that there are three dots after the path parameter :url. Otherwise its value would be escaped/encoded. This is mentioned in the documentation of path handling. Example:
m.buildPathname(':url', { url: 'http://[340f:c0e0:1d1:5gc0:g4fs:2::]:22923/backend' })
// => http%3A%2F%2F%5B340f%3Ac0e0%3A1d1%3A5gc0%3Ag4fs%3A2%3A%3A%5D%3A22923%2Fbackend
Handling URL parameters
As mentioned in the other answer, if the URL contains parameters, the question mark will be duplicated:
m.buildPathname(':url...', { url: 'https://example.com/foo?bar=baz' })
// => https://example.com/foo??bar=baz
// ^^
One way to solve that would be to include the parameters in the path template:
const url = 'https://example.com/foo?bar=baz'
const [baseUrl, params] = url.split('?')
const template = ':baseUrl...' + (params ? `?${params}` : '')
m.buildPathname(template, { baseUrl })
// => https://example.com/foo?bar=baz
However, if there are colons in the URL parameters, there's a possibility that you'll get the same error as originally ("Template parameter names *must* be separated").
There might be a way to solve this, but the previous code sample is already quite complex for this relatively simple use case. Which leads us to:
Alternative solution: don't use m.request()
m.request() is just "a thin wrapper around XMLHttpRequest." It "returns a promise and triggers a redraw upon completion of its promise chain."
If m.request() is difficult to work with due to using IPv6 URLs (or for other reasons), it can be easier to use something else for doing XHR requests. You could for example use fetch() – just remember to call m.redraw() at the end (m.request() does this automatically).
Sure, m.request() does more than just calls m.redraw() at the end (see the docs), but it's also okay to use something else.
Thanks mts knn for the reply. We have implemented your solution however we faced below issues.
Question mark is passing two times in http url of api. Please find attached below screenshot.
In order to fix this problem, please find the updated code below
async post(url, body = {}) {
var queryIndex = url.indexOf('?');
var httpPart = url.slice(0,queryIndex);
var finalUrl = url.replace(httpPart,":url...");
return new Promise((resolve, reject) => {
m.request({method: 'POST', url: finalUrl, body, params: {url: httpPart}}).then((data) => {
resolve(data);
}).catch((err) => {
reject(err.message);
});
});
}
You can also provide efficient solution If any.
I'm setting up a Booking router in NodeJS, and I have many params in.
Now when I forgot params I return an error like :
500: Need more information
I wonder if it's possible to know which params are missing when I return the error code.
This is for a new API made in NodeJS
Here are the params that I want to retrieve from the front ( made in ReactJS )
let body = {
agentDutyCode: "STRING",
RatePlanCode: params.rateCode,
RoomCode: params.roomCode,
AmountAfterTax: params.amountTax,
Start: params.fromDate,
End: params.toDate,
CardCode: params.cardCode,
CardNumber: params.cardNumber,
ExpireDate: params.expireDate,
SeriesCode: params.cvv,
CardHolderName: params.nameCard,
ChainCode: params.chainCode,
HotelCode: params.hotelCode,
RoomQuantities: params.roomQuantities,
GuestQuantitie: params.numberGuest,
GuestPerRoom: params.guestPerRoom,
LastName: params.lastName,
FirstName: params.firstName,
PhoneNumber: params.phoneNumber,
email: params.email,
FVL_SUBUNIT_7: params.walletAddress
}
And this is my promise :
cdsJson.bookResource(req.body)
.then((response) => {
if (response !== null) {
res.response = {
...response
}
} if (response.hotel.length === 0) {
res.respStatus = 500
res.response = {
sendMsg: "Need more informations"
}
next('route')
}
return response
})
If the request succeeds I got a reservation ID otherwise I got :
Error 500: Need more information
Read the documentation or the source code.
Seriously. If the API response doesn't tell you in the error message, then there is no way to know what parameters it expects programmatically.
try it for a for ... in loop like this:
cdsJson.bookResource(req.body)
.then((response) => {
if (response !== null) {
res.response = {
...response
}
} if (response.hotel.length === 0) {
res.respStatus = 500
let errorStr = "Need more informations"
for(var key in req.body) { // Get all parameters that are not set
if(objects[key] == undefined)
errorStr += "\nParameter ["+key+"] is missing!"
}
res.response = {
sendMsg: errorStr
}
next('route')
}
return response
})
You're trying to do server side validation. In Node a good approach would be to define a JSON Schema for the expected parameters and then in your route handler validate the data sent in the request with a JSON Schema validator. This would help you work out whether a request was valid and help you generate error messages automatically. As a rule it's much better (i.e. simpler and more maintainable) to use tools that enable you to declaratively declare your validation (via a schema) than imperatively write code to manually validate objects.
JSON Schema spec https://json-schema.org/
A validator https://github.com/epoberezkin/ajv
I'm just starting out with React and ES6 so apologies if this is a bit of a simple one.
I'm currently playing round with the FoursquareAPI. I'm using ES6 fetch to return a series of objects (they're actually different venues in different parts of the world) which are then mapped and returned and stored in the application's state. This works fine and returns what I want:
// method to call api
getVenues: (searchTerm) => {
const fetchVenuesURL = `${urlExplore}${searchTerm}&limit=10&client_id=${clientId}&client_secret=${clientSecret}&v=20180602`;
return fetch(fetchVenuesURL).then( response => {
return response.json();
}).then( jsonResponse => {
if (jsonResponse.response.groups[0].items) {
return jsonResponse.response.groups[0].items.map(item => (
// populate venues
{
id: item.venue.id,
name: item.venue.name,
address : item.venue.location.address,
city : item.venue.location.city,
country : item.venue.location.country,
icon : item.venue.categories[0].icon
}
));
} else {
return [];
}
});
// method in App.js to setState
search(term){
Foursquare.getVenues(term).then(foursquareResponse => {
this.setState({venues: foursquareResponse});
});
}
The problem arises when I need to fetch photographs associated with each of the 'venues' returned by the original fetch. These come from a different endpoint. I'm not sure what the best approach is.
One way would be to have two separate api calling methods and then somehow populate an empty photos field of the first with the photos from the second back in App.js but that seems clunky.
My instinct is to somehow nest the Api calls but I'm uncertain about how to go about this. I'm hoping to do something along the lines of somehow applying a method to each iteration of the first mapped object, something along the lines of but not sure how to link them together so that the second goes into the photo property of the first:
{
id: item.venue.id,
name: item.venue.name,
address : item.venue.location.address,
city : item.venue.location.city,
country : item.venue.location.country,
icon : item.venue.categories[0].icon
photos : []
}
const fetchPhotosURL = `${urlPhotos}${venueId}/photos?limit=10&client_id=${clientId}&client_secret=${clientSecret}&v=20180602`;
return fetch(fetchPhotosURL).then( response => {
return response.json();
}).then( jsonResponse => {
if (jsonResponse.response.photos.items) {
console.log(jsonResponse.response.photos.items[0].venue)
return jsonResponse.response.photos.items.map(item => (
{
id : item.id,
created: item.createdAt,
prefix: item.prefix,
suffix: item.suffix,
width: item.width,
height: item.height,
venue: item.venue
}
));
} else {
return [];
}
})
Can anyone point me in the right direction with this. I'm guessing that it's one of those things that isn't that hard once you've done it once but I'm finding it difficult.
Thanks in advance.
I'm trying the Wikipedia client login flow depicted in the API:Login docs, but something wrong happens:
1) I correctly get a token raised with the HTTP GET https://en.wikipedia.org/w/api.php?action=query&meta=tokens&type=login&format=json
and I get a valid logintoken string.
2.1) I then try the clientlogin like:
HTTP POST /w/api.php?action=clientlogin&format=json&lgname=xxxx&lgtoken=xxxx%2B%5C
and the POST BODY was
{
"lgpassword" : "xxxxx",
"lgtoken" : "xxxxx"
}
But I get an error:
{
"error": {
"code": "notoken",
"info": "The \"token\" parameter must be set."
},
"servedby": "mw1228"
}
If I try to change lgtoken to token I get the same result.
2.2) I have then tried the old method i.e. action=login and passing the body, but it does not work, since it gives me back another login token: HTTP POST https://en.wikipedia.org/w/api.php?action=login&format=json&lgname=xxxx
and the same POST BODY
I then get
{
"warnings": {}
},
"login": {
"result": "NeedToken",
"token": "xxxxx+\\"
}
where the docs here states that
NeedToken if the lgtoken parameter was not provided or no session was active (e.g. your cookie handling is broken).
but I have passed the lgtoken in the json body as showed.
I'm using Node.js and the built-in http module, that is supposed to pass and keep session Cookies in the right way (with other api it works ok).
I have found a similar issue on a the LrMediaWiki client here.
[UPDATE]
This is my current implementation:
Wikipedia.prototype.loginUser = function (username, password) {
var self = this;
return new Promise((resolve, reject) => {
var cookies = self.cookies({});
var headers = {
'Cookie': cookies.join(';'),
'Accept': '*/*',
'User-Agent': self.browser.userAgent()
};
// fetch login token
self.api.RequestGetP('/w/api.php', headers, {
action: 'query',
meta: 'tokens',
type: 'login',
format: 'json'
})
.then(response => { // success
if (response.query && response.query.tokens && response.query.tokens['logintoken']) {
self.login.logintoken = response.query.tokens['logintoken'];
self.logger.info("Wikipedia.login token:%s", self.login);
return self.api.RequestPostP('/w/api.php', headers, {
action: 'login',
format: 'json',
lgname: username
},
{
lgpassword: password,
lgtoken: self.login.logintoken
});
} else {
var error = new Error('no logintoken');
return reject(error);
}
})
.then(response => { // success
return resolve(response);
})
.catch(error => { // error
self.logger.error("Wikipedia.login error%s\n%#", error.message, error.stack);
return reject(error);
});
});
}//loginUser
where this.api is a simple wrapper of the Node.js http, the source code is available here and the api signatures are like:
Promise:API.RequestGetP(url,headers,querystring)
Promise:API.RequestPostP(url,headers,querystring,body)
If the currently accepted answer isn't working for someone, the following method will definitely work. I've used the axios library to send requests. Any library can be used but the key lies in formatting the body and headers correctly.
let url = "https://test.wikipedia.org/w/api.php";
let params = {
action: "query",
meta: "tokens",
type: "login",
format: "json"
};
axios.get(url, { params: params }).then(resp => {
let loginToken = resp.data.query.tokens.logintoken
let cookie = resp.headers["set-cookie"].join(';');
let body = {
action: 'login',
lgname: 'user_name',
lgpassword: 'password',
lgtoken: loginToken,
format: 'json'
}
let bodyData = new URLSearchParams(body).toString();
axios.post(url, bodyData, {
headers: {
Cookie: cookie,
}
}).then(resp => {
// You're now logged in!
// You'll have to add the following cookie in the headers again for any further requests that you might make
let cookie = resp.headers["set-cookie"].join(';')
console.log(resp.data)
})
})
And you should be seeing a response like
{
login: { result: 'Success', lguserid: 0000000, lgusername: 'Username' }
}
The second post request was where I got stuck for several hours, trying to figure out what was wrong. You need to send the data in an encoded form by using an API like URLSearchParams, or by just typing up the body as a string manually yourself.
I think from what you are saying you have lgtoken and lgname in the URL you are using, and then lgpassword and lgtoken (again!) in a JSON-encoded POST body.
This is not how the Mediawiki API works.
You submit it all as POST parameters. JSON is never involved, except when you ask for the result to come back in that format. I can't help you fix your code as you don't provide it, but that's what you need to do. (If you edit your question with your code, I'll do my best to help you.)
After seeing your code, I'll presume (without knowing the detail of your code) that you want something like this:
return self.api.RequestPostP('/w/api.php', headers, {
action: 'login',
format: 'json',
lgname: username,
lgpassword: password,
lgtoken: self.login.logintoken
});