I'm trying to POST a request to https://actions.googleapis.com/v3/packages/{packageName}/skus:batchGet as described in section 2. b. in the non-consumable digital transactions guide. Pasting the relevant snippet here:
return jwtClient.authorize((err, tokens) => {
if (err) {
throw new Error(`Auth error: ${err}`);
}
const packageName = 'com.example.projectname';
request.post(`https://actions.googleapis.com/v3/packages/${packageName}/skus:batchGet`, {
'auth': {
'bearer': tokens.access_token,
},
'json': true,
'body': {
'conversationId': conversationId,
'skuType': 'APP',
// This request is filtered to only retrieve SKUs for the following product IDs
'ids': ['nonconsumable.1']
},
}, (err, httpResponse, body) => {
if (err) {
throw new Error(`API request error: ${err}`);
}
console.log(`${httpResponse.statusCode}: ${httpResponse.statusMessage}`);
console.log(JSON.stringify(body));
});
});
});
The request body should have a conversationId field. While this field exists in the Dialogflow and legacy Actions SDK, it's missing from the new Actions SDK webhook requests as far as I can tell.
The new Actions SDK documentation links to that digital transactions guide so I have assumed it should be compatible, but have found no mention of required adaptations to be able to use it.
So my question is, how can that conversationId be fetched when making transactions from a webhook fulfilling requests from the new Actions SDK?
The snippet provided in the documentation is incorrect.
Please use the session ID. You can access this value via conv.session.id.
Related
I'm using the Payouts API and to do so I have to specify an access token when calling https://api-m.sandbox.paypal.com/v1/payments/payouts
To get an access token, as specified here I have to send a POST request with Postman to https://developer.paypal.com/docs/api/overview/#get-an-access-token and it returns in the response the token
My problem is that this token has an expiration time, which is an issue because I can't go in my app every 15 minutes to change the token in the Authorization header. How can I get a "permanent" or rather an "auto refreshed" token ?
I tried to make a call from my app instead of Postman but it doesn't seem to work, it says my credentials are invalid
This is my code in case it can be useful (I'm managing everything from the front-end):
window.paypal
.Buttons({
createOrder: (data, actions) => {
return actions.order.create({
purchase_units: [
{
description: this.product.description,
amount: {
value: this.product.price
}
}
]
});
},
onApprove: (data, actions) => {
const timestamp = new Date().getUTCMilliseconds();
const id = timestamp;
return actions.order.capture().then(details => {
axios
.post(
"https://api-m.sandbox.paypal.com/v1/payments/payouts",
{
sender_batch_header: {
sender_batch_id: id,
email_subject: "You have a payout!",
email_message:
"You have received a payout! Thanks for using our service!",
recipient_type: "EMAIL"
},
items: [
{
amount: {
value: this.product.price,
currency: "USD"
},
note: "congrats someone bought your stuff",
sender_item_id: id,
receiver: "john#doe.com"
}
]
},
{
headers: {
Authorization:
"Bearer <my token that I get from postman>"
}
}
)
.then(() => {
alert(
"Transaction completed by " + details.payer.name.given_name
);
})
.catch(err => console.log(err));
});
}
})
.render(".paypal");
}
},
Update
Reading the code a second time and noticing how your Payout is for the same this.product.price as the original payment, that makes no sense. Don't use payouts at all for this use case. The full amount will go to your account normally, but if you want the full amount to go somewhere else instead, set the payee variable for this checkout: https://developer.paypal.com/docs/checkout/integration-features/pay-another-account/
(original answer below)
Using Payouts from the client side is an absolutely terrible idea, anyone with your client credentials will be able to send money from your account to wherever they want
The payouts API must be used from your server only. Your server needs to do an API call to get an access token, similar to what you mention about postman -- except using whatever backend environment you have. You can integrate direct HTTPS calls, or there are also Payouts SDKs available which will handle getting the access token automatically.
As for how to trigger that backend action when capturing a payment, use a proper server integration:
Create two routes, one for 'Create Order' and one for 'Capture Order', documented here. These routes should return JSON data. Before returning JSON data, that last route (the capture one) should record the successful transaction in your database and trigger any Payout logic you want.
Pair those two routes with the following approval flow: https://developer.paypal.com/demo/checkout/#/pattern/server
I am querying a collection in MongoDB from Dialoglow Fulfillment. I then want my bot to respond with a message which includes this query. The code in the function of the Dialogflow Fulfillment is:
function readRecord(agent){
var name;
MongoClient.connect(uri, function(err, client) {
const collection = client.db("test").collection("data");
collection.find({fname: 'Example'}).toArray(function(err, result){
if (err) throw err;
console.log(result);
name = result.lname;
agent.add("Found last name: ", name);
});
client.close();
});
}
When I run this I get no response from my from the bot. When I console.log(result) the information is there but I can't seem to get the bot to say it.
The issue is that the intent handler expects you to return a Promise if you are doing any asynchronous functions - like accessing a database. The easiest way to do this is to change from using callbacks with MongoDB to using versions of the functions that return Promises, and then to return the promise.
I haven't tested, but something like this might work
return MongoClient.connect( uri )
.then( client => {
const collection = client.db("test").collection("data");
return collection.find({fname: 'Example'}).toArray();
})
.then( result => {
let name = result[0].lname;
agent.add("Found last name: "+name);
});
I'm having trouble communicating between the frontend and backend for a selected GET request.
I am using a React frontend with an express/mongoose setup out in the backend.
In the frontend, I do a GET call using axios for:
axios.get('/api/orders/', {
params : {
name: this.props.user.name // user name can be Bob
}
})
And in the backend I'm having a hard time understanding the correct method I would need to do to query the database (example below doesn't work). I found stuff with .select but even then I still can't get it to work:
router.get('/orders', function(req, res) {
Order.find({}).select(req.params).then(function (order) {
res.send(req.params);
})
});
I also tried doing this to see if I can even get the params to send properly and to no demise:
router.get('/orders/:name', function(req, res) {
res.send('client sent :',req.query.name);
});
The orders document model holds objects that house an ordered array and a name (type: String) attached to the object. The Mongoose scheme for the order:
const orderScheme = new Schema({
name : { type : String },
orders : { type : Array}
});
In my MongoDB, I can see all the "Master Orders" send back. Each master order has the name of who submitted it, plus all the orders within (there can be a ton of orders).
What I'm trying to exactly do is pull up all orders that have a certain name. So if I search "TestAccount", I'll get all of bob's orders. I've included an image below:
Any pointers?
Client-side:
axios.get('/api/orders/' + this.props.user.name)
.then(function (response) {
// handle success
console.log(response);
})
.catch(function (error) {
// handle error
console.log(error);
})
You need to handle the Promise when resolved/rejected.
Server-side:
router.get('/orders/:name', function(req, res) {
return Order.find({name: req.params.name}).then(function(orders) {
// return orders when resolved
res.send(orders);
})
.catch(function (error) {
// handle error
console.log(error);
})
});
You did not specify a named route parameter in your route path.
You also aren't accessing the name property by using req.params only.
You should use Model.find() conditions parameter to specify which document[s] you're trying to find. Query.prototype.select() is for filtering document fields.
I am trying to make an assistant app and was using the cloud firestore service of firebase to send the response back to my app using webhook as fulfilment. I have used 'session' parameter in request JSON according to this documentation and sending fulfilmentText as response to the user. But whenever user launches the app, a new session is created which I don't want. I simply want, just a single entry for each user in my database so how to achieve that using dialogflow.
In Alexa Skill, we have deviceId as parameter by which we can uniquely identify the user irrespective of the session id but is there any parameter in the dialogflow request JSON. If not, then how to achieve this task without it.
The request JSON I am getting from Dialogflow has a userID in it, so can I use the userId or should I go with userStorage provided the userStorage parameter is not available in the request JSON.
request.body.originalDetectIntentRequest { source: 'google', version: '2', payload: { surface: { capabilities: [Object] },
inputs: [ [Object] ],
user:
{ locale: 'en-US',
userId: 'ABwppHG5OfRf2qquWWjI-Uy-MwfiE1DQlCCeoDrGhG8b0fHVg7GsPmaKehtxAcP-_ycf_9IQVtUISgfKhZzawL7spA' },
conversation:
{ conversationId: '1528790005269',
type: 'ACTIVE',
conversationToken: '["generate-number-followup"]' },
availableSurfaces: [ [Object] ] } }
EDIT : Thank You #Prisoner for the answer but I am unable to send the random ID generated in the response and set in in the payload. Below is the code where I am generating the uuid and storing it in firestore. What I am doing wrong in the below code due to which new uuid is generated for returning user and therefore response is shown as No document found in the database. I suppose I am not sending uuid appropriately. Please help.
exports.webhook = functions.https.onRequest((request, response) => {
console.log("request.body.queryResult.parameters", request.body.queryResult.parameters);
console.log("request.body.originalDetectIntentRequest.payload", request.body.originalDetectIntentRequest.payload);
let userStorage = request.body.originalDetectIntentRequest.payload.user.userStorage || {};
let userId;
console.log("userStorage", userStorage);
if (userId in userStorage) {
userId = userStorage.userId;
} else {
var uuid = require('uuid/v4');
userId = uuid();
userStorage.userId = userId
}
console.log("userID", userId);
switch (request.body.queryResult.action) {
case 'FeedbackAction': {
let params = request.body.queryResult.parameters;
firestore.collection('users').doc(userId).set(params)
.then(() => {
response.send({
'fulfillmentText' : `Thank You for visiting our ${params.resortLocation} hotel branch and giving us ${params.rating} and your comment as ${params.comments}.` ,
'payload': {
'google': {
'userStorage': userStorage
}
}
});
return console.log("resort location", params.resortLocation);
})
.catch((e => {
console.log('error: ', e);
response.send({
'fulfillmentText' : `something went wrong when writing to database`,
'payload': {
'google': {
'userStorage': userStorage
}
}
});
}))
break;
}
case 'countFeedbacks':{
var docRef = firestore.collection('users').doc(userId);
docRef.get().then(doc => {
if (doc.exists) {
// console.log("Document data:", doc.data());
var dat = doc.data();
response.send({
'fulfillmentText' : `You have given feedback for ${dat.resortLocation} and rating as ${dat.rating}`,
'payload': {
'google': {
'userStorage': userStorage
}
}
});
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
response.send({
'fulfillmentText' : `No feedback found in our database`,
'payload': {
'google': {
'userStorage': userStorage
}
}
});
}
return console.log("userStorage_then_wala", userStorage);
}).catch((e => {
console.log("Error getting document:", error);
response.send({
'fulfillmentText' : `something went wrong while reading from the database`,
'payload': {
'google': {
'userStorage': userStorage
}
}
})
}));
break;
}
You have a couple of options, depending on your exact needs.
Simple: userStorage
Google provides a userStorage object which is persisted across conversations when it can identify a user. This lets you store your own identifier when you need to track when a user returns.
The easiest way to do this is to check the userStorage object for the identifier when your webhook is called. If it doesn't exist, create one using something like a v4 UUID and save it in the userStorage object.
If you are using the actions-on-google library, the code might look something like this:
let userId;
// if a value for userID exists un user storage, it's a returning user so we can
// just read the value and use it. If a value for userId does not exist in user storage,
// it's a new user, so we need to generate a new ID and save it in user storage.
if (userId in conv.user.storage) {
userId = conv.user.storage.userId;
} else {
// Uses the "uuid" package. You can get this with "npm install --save uuid"
var uuid = require('uuid/v4');
userId = uuid();
conv.user.storage.userId = userId
}
If you are using the dialogflow library, you can use the above, but you'll need this line first:
let conv = agent.conv();
If you're using the multivocal library, it does all of the above for you and will provide a UserID in the environment under the path User/Id.
If you're handling the JSON directly, and you are using the Dialogflow v2 protocol, you can get the userStorage object by examining originalDetectIntentRequest.payload.user.userStorage in the JSON request object. You'll set the payload.google.userStorage object in the JSON response. The code is similar to the above and might look something like this:
let userStorage = body.originalDetectIntentRequest.payload.user.userStorage || {};
let userId;
// if a value for userID exists un user storage, it's a returning user so we can
// just read the value and use it. If a value for userId does not exist in user storage,
// it's a new user, so we need to generate a new ID and save it in user storage.
if (userId in userStorage) {
userId = userStorage.userId;
} else {
// Uses the "uuid" package. You can get this with "npm install --save uuid"
var uuid = require('uuid/v4');
userId = uuid();
userStorage.userId = userId
}
// ... Do stuff with the userID
// Make sure you include the userStorage as part of the response
var responseBody = {
payload: {
google: {
userStorage: JSON.stringify(userStorage),
// ...
}
}
};
Note the first line of the code - if userStorage doesn't exist, use an empty object. It won't exist until you send a response that includes storing something in it for the first time, which will happen in the last few lines of this code.
Advanced: Account Linking
You can request users to sign in to your Action using Google Sign In. This can be done just using voice for the simplest of cases and would only interrupt the flow the first time.
After this, your Action is given a JWT which contains their Google ID which you can use as their identifier.
If you're using the actions-on-google library, you can get the ID from the decoded JWT with a line such as:
const userId = conv.user.profile.payload.sub;
In the multivocal library, the ID from the decoded JWT is available in the environment under the path User/Profile/sub
Deprecated: Anonymous User ID
You'll see some answers here on StackOverflow that reference an Anonymous User ID. Google has deprecated this identifier, which was not always a reliable way to verify returning users, and will be removing it 1 Jun 2019.
This code is currently still being sent, but this will be removed starting 1 Jun 2019.
So, I'm trying to show how Restivus API works and Meteor is nice to my colleagues. :)
I've made a simple blog app at http://askar-blog.meteor.com/ (thanks to DiscoverMeteor book).
My repo https://github.com/tenzan/blog
(I'm reading https://github.com/kahmali/meteor-restivus#restivus)
I have three collections:
users
posts
comments
So, post has many comments. Usually, we used to have comments as a nested documents inside of a post, but from the Meteor's nature these two attributes are split up into different collections.
I want to implement a REST API, so that I can access (including CRUD operations) posts and collections in the way:
http://example.com/api/posts - all posts
http://example.com/api/posts/post_id - a specific post
http://example.com/api/posts/post_id/comments - all comments that belongs to a given post
http://example.com/api/posts/post_id/comments/comment_id - a specific comment that belongs to a given post
If you have a look at my repo, you will see there're posts.js and comments.js under lib/collections.
As I understood, to enable REST API, I will need the following snippet in the posts.js:
if (Meteor.isServer) {
// Global API configuration
var Api = new Restivus({
useDefaultAuth: true,
prettyJson: true
});
// Generates: GET, POST on /api/post and GET, PUT, DELETE on
// /api/items/:id for the Posts collection
Api.addCollection(Posts);
// Generates: POST on /api/users and GET, DELETE /api/users/:id for
// Meteor.users collection
Api.addCollection(Meteor.users, {
excludedEndpoints: ['getAll', 'put'],
routeOptions: {
authRequired: true
},
endpoints: {
post: {
authRequired: false
},
delete: {
roleRequired: 'admin'
}
}
});
As you see, I've added Api.addCollection(Posts); and I've confirmed I can access all posts or a specific one.
My questions:
1- How can I setup API to access comments for their parent post?
2 - Will I have to have to following code to access posts ? I'm asking because, I'm already able to access them as I have Api.addCollection(Posts); :
Maps to: /api/posts/:id
Api.addRoute('posts/:id', {authRequired: true}, {
get: function () {
return Posts.findOne(this.urlParams.id);
},
delete: {
roleRequired: ['author', 'admin'],
action: function () {
if (Articles.remove(this.urlParams.id)) {
return {status: 'success', data: {message: 'Post removed'}};
}
return {
statusCode: 404,
body: {status: 'fail', message: 'Post not found'}
};
}
}
});
I apologise, I got confused myself trying to figure out the correct way of making a REST API.
Please feel free to add anything important on this regard I have missed here.
I've discussed with the author of the package
https://github.com/kahmali/meteor-restivus/issues/128
The feature is being developed
https://github.com/kahmali/meteor-restivus/issues/70