register webhooks on nodejs when order created - node.js

I have a shopify store mystore and I have an nodejs app myapp. I need to do is when something happens on mystore a webhook will be created/registered in my nodejs app. I have tried https://www.npmjs.com/package/#shopify/koa-shopify-webhooks this package but it is not working for me and I don't think that it is the same thing that I want. I just want that when let suppose order is created in store a webhook is registered.

if you just have to register a webhook you can use this code.
You just have to change the webhook topic and the endpoint.
This is for orders/create webhook registration
add shopify-api-node and request-promise packages and require them
const ShopifyAPIClient = require("shopify-api-node");
const request = require("request-promise");
then
const createOrderWebhook = await registerWebhook(yourShopDomain, yourShopAccessToken, {
topic: "orders/create",
address: "Your node app end point" //www.example.com/webhooks/createOrder,
format: "json",
});
add your registerWebhook function
const registerWebhook = async function (shopDomain, accessToken, webhook) {
const shopify = new ShopifyAPIClient({
shopName: shopDomain,
accessToken: accessToken,
});
const isCreated = await checkWebhookStatus(shopDomain, accessToken, webhook);
if (!isCreated) {
shopify.webhook.create(webhook).then(
(response) => console.log(`webhook '${webhook.topic}' created`),
(err) =>
console.log(
`Error creating webhook '${webhook.topic}'. ${JSON.stringify(
err.response.body
)}`
)
);
}
};
for checking the webhook already not created at Shopify you can use following code
const checkWebhookStatus = async function (shopDomain, accessToken, webhook) {
try {
const shopifyWebhookUrl =
"https://" + shopDomain + "/admin/api/2020-07/webhooks.json";
const webhookListData = {
method: "GET",
url: shopifyWebhookUrl,
json: true,
headers: {
"X-Shopify-Access-Token": accessToken,
"content-type": "application/json",
},
};
let response = await request.get(webhookListData);
if (response) {
let webhookTopics = response.webhooks.map((webhook) => {
return webhook.topic;
});
return webhookTopics.includes(webhook.topic);
} else {
return false;
}
} catch (error) {
console.log("This is the error", error);
return false;
}
};
Happy coding :)

You can not create/register a new webhook when the order created.
Webhooks are a tool for retrieving and storing data from a certain event. They allow you to register an https:// URL where the event data can be stored in JSON or XML formats. Webhooks are commonly used for:
Placing an order
Changing a product's price
Notifying your IM client or your pager when you are offline
Collecting data for data-warehousing
Integrating your accounting software
Filtering the order items and informing various shippers about the order
Removing customer data from your database when they uninstall your app

Related

Oauth2 with Google docs API Node.js (trying to programmatically write a new google doc)

I have a typical web app with a client and a node.js server. When a user selects an option on the client, I want to export (create) a google doc in their drive.
I am half way there following this tutorial https://developers.google.com/identity/protocols/oauth2/web-server
With my current set up, after the user authenticates, the authentication token is sent to a web hook (server side), but I don't have any of the data for creating the google doc there.
If I did, I could create the doc from there. Otherwise, I need to send the token itself back to the client so I can create the doc with the necessary payload from there.
In that case, I don't know how to signal to the client that the user has been authenticated. Should I use a web socket?
Something tells me that my general set up might not be correct, and that I should be doing it a different way in my use case.
This is my client side code that brings the user to the google auth page after getting the auth url (not sure if this really needs to be done server side, but since I have some user credentials I thought it might be better).
export async function exportToGoogleDoc() {
const response = await POST(`${API_URL}export/gdoc`, {
'hello': 'world'
});
if (response.status == 200){
window.location.href = response.authUrl;
}
}
And then the endpoint (just returns the autheticationUrl)
api.post('/export/gdoc', express.raw({ type: 'application/json' }), async (req, res, next) => {
try {
const scopes = [
'https://www.googleapis.com/auth/drive'
];
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
const authorizationUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes,
include_granted_scopes: true
});
res.json({ 'status': 200, authUrl : authorizationUrl } );
} catch (err){
next(err);
}
});
But then after the user agrees and authenticates with their google account, the auth token is sent to this web hook. At the bottom I am able to write an empty google doc to the authenticated google drive, but I don't have the data I need to create the real doc.
api.get('/auth/google', express.raw({ type: 'application/json' }), async (req, res, next) => {
const q = url.parse(req.url, true).query;
const oauth2Client = new google.auth.OAuth2(
credentials.web.client_id,
credentials.web.client_secret,
credentials.web.redirect_uris[0]
);
if (q.error) {
console.log('Error:' + q.error);
} else {
const { tokens } = await oauth2Client.getToken(q.code.toString());
oauth2Client.setCredentials(tokens);
const drive = google.drive('v3');
const requestBody = {
'name': 'Dabble',
'mimeType': 'application/vnd.google-apps.document',
};
drive.files.create({
requestBody: requestBody,
fields: 'id',
auth: oauth2Client
}, function (err, file) {
if (err) {
// Handle error
console.error(err);
} else {
console.log('File Id: ', file);
}
});
}
Somehow I either need to get the data for the google doc inside this web hook, or to listen for this web hook from the client.
OR I need an entirely different set up. I realize I also should be probably storing this token somewhere (local storage on client side?) and only making this call if they do not have a token.
Can anyone help me modify my set up? Thanks!

Why do I receive 402 code for request when implementing Digital Goods Purchase for Actions on Google?

My Assistant app gets a status of PEMISSION_DENIED with a message saying that
the caller does not have permission
I have followed all instructions here by this person: Digital Goods Implementation
I have created APK, managed SKU (valid status) items, Android app that became published (valid status), enabled Actions API from Google API Console and made a service account key for my project, and released my Assistant app for beta to test it. The Connect App status is connected and the toggle is on. I think everything required is completed but the error message does not get fixed, which means I cannot receive purchasable items I prepared in my Google Developer Console.
const createJwtClient = () => {
const scopes = [
"https://www.googleapis.com/auth/actions.purchases.digital"
];
return new google.auth.JWT(
serviceAccount.client_email,
null,
serviceAccount.private_key,
scopes,
null
);
};
const packageName = 'com.myproject.name'
const getSkus = (tokens, conv) => {
return new Promise((resolve, reject) => {
const url = `https://actions.googleapis.com/v3/packages/${packageName}/skus:batchGet`;
const convId = conv.request.conversation.conversationId;
const param = {
conversationId: convId,
skuType: "SKU_TYPE_IN_APP",
ids: [
"item-id1",
"item-id2"
]
};
request.post(url, {
auth: {
bearer: tokens.access_token
},
json: true,
body: param
}, (err, httpResponse, body) => {
if (err) {
reject(err);
} else {
const statusCode = httpResponse.statusCode;
const statusMessage = httpResponse.statusMessage;
console.log(`${statusCode}: ${statusMessage}`);
console.log(JSON.stringify(body));
resolve(body);
}
});
});
};
I expect I get purchasable items as JSON, but my request to fetch SKU seems to fail.
But I do receive access_token from JWT request using my service account key.
Could you point out something that is wrong??
First, make sure your testing email is different from the Play Console Admin's email. Second, ensure you've added managed products and subscriptions to the Google Play Console (see below) The Actions on Google Github sample has been recently updated as well and the Kotlin APK has all the specifics in the 'Getting Started' section of the readme.

Rendering Current Page With PhantomJS

I am building an analytics dashboard using the MERN stack (Express, Node) are the Important things to highlight.
As part of a dash view, I was trying to find if it's possible to trigger a PhantomJS call to create a pdf report using a button on the page itself.
Given you need to be logged in to see your own analytics, I can not just run phantom from the command line and pass it in the URL of one of the dashboard pages since it requires a login and queries to be made.
Is it possible to do this with phantomJS?
If I correctly understood your question.
Example:
[main.js]
const dashboardToPdfCtrl = require("./controllers/phantom/pdf");
router.route("/api/dashboard/phantom").post(dashboardToPdfCtrl.createPdf);
router.route("/api/dashboard/phantom/html")
.post(dashboardToPdfCtrl.createDashboard);
When the user clicks on the "button" you can validate the USER according to the architecture of your application.
[pdf.js]
exports.createPdf= async (req, res) => {
if (!req.user || !req.user.sub) {
return res
.status(401)
.send({ message: 'No authorization token was found' });
}
const instance = await phantom.create();
const page = await instance.createPage();
const settings = {
operation: "POST",
encoding: "utf8",
headers: {
"Content-Type": "application/json"
},
data: JSON.stringify({
user: req.body.userId,
anyDataYouNeedToRender: req.body.anyDataYouNeedToRender
})
};
//POST request to /api/dashboard/phantom/html
await page.open(
`${process.env.HOST}:${
process.env.PORT
}/api/dashboard/phantom/html`,
settings
);
//Save the content of /public/dashboard/dashboard.html with received data to pdf
const pageSaved = await page.render(
path.resolve(`./public/dashboard/file.pdf`)
);
if (pageSaved) await instance.exit();
}
exports.createDashboard = (req, res) => {
res.render(
path.resolve("./public/dashboard/dashboard.html"),
{ user: req.body.user,
anyDataYouNeedToRender: req.body:anyDataYouNeedToRender
}
);
};
Is that what you were looking for? I want to help you, feel free to ask detalization.
P.S. As friends told before in comments, it will be great if you give us more information to understend you goal.

Authentication Error when Retrieving and Editing Device Configuration on IoT-Core

I'm trying to use a backend nodeJS server to access (and edit) the device configuration on IoT-Core referring to this API docs
However, I keep getting error:
code 401 with error message "message": "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status": "UNAUTHENTICATED".
I created a service account and a key from Google IAM, and gave it Cloud IoT Device Controller permissions, which could update device configurations but not create or delete. Subsequently, I changed it to Cloud IoT Admin and even Project Editor permissions, but still saw the same error message. Am I getting the keys all wrong, or not doing something else I should be doing?
Code below was how I invoked the request
function createJwt (projectId, privateKeyFile, algorithm) {
// Create a JWT to authenticate this device. The device will be disconnected
// after the token expires, and will have to reconnect with a new token. The
// audience field should always be set to the GCP project ID.
const token = {
'iat': parseInt(Date.now() / 1000),
'exp': parseInt(Date.now() / 1000) + 20 * 60, // 20 minutes
'aud': projectId
};
const privateKey = fs.readFileSync(privateKeyFile);
return jwt.sign(token, privateKey, { algorithm: algorithm });
}
app.get('/', function(req, res){
let authToken = createJwt('test-project', './keys/device-config.pem', 'RS256');
const options = {
url: 'https://cloudiot.googleapis.com/v1/projects/test-project/locations/us-central1/registries/dev-registry/devices/test-device',
headers: {
'authorization': 'Bearer ' + authToken,
'content-type': 'application/json',
'cache-control': 'no-cache'
},
json: true
}
request.get(options, function(error, response){
if(error) res.json(error);
else res.json(response);
})
});
For backend servers to interact with IoT-Core, the authentication method is not the same as for device MQTT or HTTP connections. Reference: https://cloud.google.com/iot/docs/samples/device-manager-samples#get_a_device
I was able to retrieve and update device configurations using the code below
function getClient (serviceAccountJson, cb) {
const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountJson));
const jwtAccess = new google.auth.JWT();
jwtAccess.fromJSON(serviceAccount);
// Note that if you require additional scopes, they should be specified as a
// string, separated by spaces.
jwtAccess.scopes = 'https://www.googleapis.com/auth/cloud-platform';
// Set the default authentication to the above JWT access.
google.options({ auth: jwtAccess });
const DISCOVERY_API = 'https://cloudiot.googleapis.com/$discovery/rest';
const API_VERSION = 'v1';
const discoveryUrl = `${DISCOVERY_API}?version=${API_VERSION}`;
google.discoverAPI(discoveryUrl, {}, (err, client) => {
if (err) {
console.log('Error during API discovery', err);
return undefined;
}
cb(client);
});
}
function getDevice (client, deviceId, registryId, projectId, cloudRegion) {
const parentName = `projects/${process.env.GCP_PROJECT_ID}/locations/${cloudRegion}`;
const registryName = `${parentName}/registries/${registryId}`;
const request = {
name: `${registryName}/devices/${deviceId}`
};
const promise = new Promise(function(resolve, reject){
client.projects.locations.registries.devices.get(request, (err, data) => {
if (err) {
console.log('Could not find device:', deviceId);
console.log(err);
reject(err);
} else {
console.log(data.config.binaryData);
resolve(data);
}
});
});
return promise;
}
app.get('/', function(req, res){
const cb = function(client){
getDevice(client, 'test-device', 'dev-registry', process.env.GCP_PROJECT_ID, 'us-central1')
.then(function(response){
let decoded = new Buffer(response.config.binaryData, 'base64').toString();
res.json(decoded);
})
.catch(function(error){
res.json(error);
})
}
getClient(serviceAccountJson, cb);
});
I think what you're looking to do is best accomplished using the client library for NodeJS.
First, retrieve an API client object as done in the sample. This will take in the service account credentials you used and will authenticate against Google API Core servers.
At the point in the referenced code where cb(client); is invoked, you'll have your client object and are ready to update your device. Add the imports and API constants from the sample and replace the code where you have a client object with the following code and you should be set.
Use some strings for your device identifiers:
const projectId = 'my-project';
const cloudRegion = 'us-central1';
const registryId = 'my-registry';
const deviceId = 'my-device;
const config = '{fan: 800}';
Next, form your device String:
const deviceId = `projects/${projectId}/locations/${cloudRegion}/registries/${registryId}/devices/${deviceId}`;
const binaryData = Buffer.from(config).toString('base64');
Now you form your request object and execute:
const request = {
name: `${registryName}`,
versionToUpdate: 0,
binaryData: binaryData
};
console.log(request);
client.projects.locations.registries.devices
.modifyCloudToDeviceConfig(
request,
(err, data) => {
if (err) {
console.log('Could not update config:', deviceId);
console.log('Message: ', err);
} else {
console.log('Success :', data);
}
});
Your configuration is updated. If your device is subscribed to the config topic on MQTT it will receive the latest configuration, otherwise, you can poll for the configuration with HTTP from your device.
Just to confirm, when you created the SSL key pair, and when you registered the device with the Cloud IoT Core registry, did you match the type of key created with the radio button you registered it with?
Also to confirm, you put the Google root certificate on the device in the same directory as the private key: ./keys/device-config.pem ? If not you can fetch it with: wget https://pki.google.com/roots.pem.

Send a notification by Lambda function with AWS Pinpoint

I have an iOS app with a backend build by MobileHub with API Gateway, Lambda, and DynamoDB.
I noticed that the SNS function of MobileHub has been replaced by Pinpoint, and I would like to create a notification system using this new service.
When a user creates a new post through API Gateway, a lambda function will be triggered and I suppose I can send a notification to the subscribers by Pinpoint.
But I cannot find any example or reference doc in the official website of Pinpoint.
Do you have any resource for this scenario or any idea? Thank you very much!
Depends what you mean by notification, I assume you would like to send a push notification to a particular user (Pinpoint endpoint).
Pinpoint stores each device associated with a user as an "endpoint", generally created by the AWS client side analytics library (e.g. amplify analytics).
The client
With the amplify analytics library, I call updateEndpoint so I can specify a userId that is available to Lambda, as well as the device token and remove optOut so user can receive the push notification:
Address - The token generated from user accepting push notification permission (iOS)
optOut - NONE so they can receive push notifications
userId - unique id for user (Cognito's sub)
Lambda (node.js)
Now you can send a push notification, using the userId and the Pinpoint SDK.
Example:
const sendMessagesParams = {
ApplicationId: process.env.PINPOINT_APP_ID,
SendUsersMessageRequest: {
Users: {
[receiverUserId]: {}
},
MessageConfiguration: {
APNSMessage: {
Action: 'OPEN_APP',
Title: 'Message received',
SilentPush: false,
Body: `You have a new message`
},
GCMMessage: {
Action: 'OPEN_APP',
Title: 'Message received',
SilentPush: false,
Body: `You have a new message`
}
}
}
};
console.log('sendMessagesParams', JSON.stringify(sendMessagesParams));
pinpoint.sendUsersMessages(sendMessagesParams, (sendMessagesErr, sendMessagesData) => console.log('push sent')
For your particular scenario, I set up a DynamoDB stream and trigger a Lambda when a record changes within the table. You may need to add the IAM permissions manually once the lambda is created.
Sources
Full list of pinpoint methods you can use in lambda (Node.JS)
Update endpoint using Amplify Analytics (JS)
Dynamodb streams - trigger a lambda
I was struggling a lot getting a lambda function working, so please see this answer as an addition to the answer from Dylan w.
Client
import PushNotification from '#aws-amplify/pushnotification';
import Analytics from '#aws-amplify/analytics';
PushNotification.onRegister((token) => {
Analytics.updateEndpoint({
address: token,
channelType: 'APNS',
optOut: 'NONE',
// Customized userId
userId: "e236e3ea-bas9-4eae-967e-0eb9bcaca26d" // Example
})
});
Lambda function
'use strict';
const AWS = require('aws-sdk');
exports.handler = async (event, context) => {
var pinpoint = new AWS.Pinpoint();
const sendMessagesParams = {
ApplicationId: <YOUR_APPLICATION_ID>, // Find it in Pinpoint->All projects
SendUsersMessageRequest: {
Users:{<USER_ID>:{}}, // The same userId as set on the client. This way you can "follow" people if they switch device
MessageConfiguration:{
APNSMessage:{
Action:"OPEN_APP",
Title:"Message received",
Body:"You have a new message"
}
}
}
};
return await new Promise( (resolve, reject) => {
pinpoint.sendUsersMessages(sendMessagesParams, (sendMessagesErr, sendMessagesData) => {
if(sendMessagesErr) reject(sendMessagesErr)
if(sendMessagesData) resolve(sendMessagesData)
});
});
};
Note that the call to pinpoint is wrapped in a promise. Because pinpoint.sendUserMessages accepts a callback, the execution is continued (async nature of Node), which will shut down the lambda function and you will get no output from the callback function or receive a notification, without awaiting the function to finish.
Finally, I've got something that perfectly works.
The answer is that you have to use "targetClient" to update endpoint inside "didRegisterForRemoteNotificationsWithDeviceToken" function.
let client = self.pinpoint!.targetingClient
let profile = client.currentEndpointProfile()
print("EndpointId = \(profile.endpointId)")
profile.user?.userId = <YOUR_CUSTOM_ID>
client.update(profile)
Client Side(XCODE)
Here is my [AppDelegate.swift] looks like:
(Important part is inside "didRegisterForRemoteNotificationsWithDeviceToken" function)
import UserNotifications
import AWSPinpoint
class AppDelegate: UIResponder, UIApplicationDelegate {
var pinpoint: AWSPinpoint?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Instantiate Pinpoint
let pinpointConfiguration = AWSPinpointConfiguration.defaultPinpointConfiguration(launchOptions: launchOptions)
// Set debug mode to use APNS sandbox, make sure to toggle for your production app
pinpointConfiguration.debug = true
self.pinpoint = AWSPinpoint(configuration: pinpointConfiguration)
// Present the user with a request to authorize push notifications
self.registerForPushNotifications()
return true
}
func registerForPushNotifications() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { [weak self] granted, _ in
print("Permission granted: \(granted)")
guard granted else { return }
// Only get the notification settings if user has granted permissions
self?.getNotificationSettings()
}
}
func getNotificationSettings() {
UNUserNotificationCenter.current().getNotificationSettings { settings in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
DispatchQueue.main.async {
// Register with Apple Push Notification service
UIApplication.shared.registerForRemoteNotifications()
}
}
}
func application(_: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
print("DidRegisterForRemoteNotificationsWithDeviceToken: Start")
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
// Register the device token with Pinpoint as the endpoint for this user
self.pinpoint!.notificationManager.interceptDidRegisterForRemoteNotifications(withDeviceToken: deviceToken)
//set custom userId and update endpoint
let client = self.pinpoint!.targetingClient
let profile = client.currentEndpointProfile()
print("EndpointId = \(profile.endpointId)")
profile.user?.userId = <YOUR_CUSTOM_ID>
client.update(profile)
}
func application(_: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register: \(error)")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
print("didReceiveRemoteNotification:\(userInfo)")
// Pass this remote notification event to pinpoint SDK to keep track of notifications produced by AWS Pinpoint campaigns.
self.pinpoint!.notificationManager.interceptDidReceiveRemoteNotification(userInfo)
}
}
Backend (Lambda with Nodejs)
And here is a code to send a notification to specific user.
'use strict';
const AWS = require('aws-sdk');
const pinpoint = new AWS.Pinpoint({region: 'us-west-2'});
exports.handler = (event, context, callback) => {
const done = (err, data) => {
if(err){
console.log('ERROR:', err);
const response = {
statusCode: 400,
body: JSON.stringify(err)
};
callback(response);
}else{
console.log('SUCCESS:', data);
const response = {
statusCode: 200,
body: JSON.stringify(data)
};
callback(null, response);
}
};
let users = {};
users[<YOUR_CUSTOM_ID>] = {};
const params = {
ApplicationId: PINPOINT_PROJECT_ID,
SendUsersMessageRequest: {
Users: users,
MessageConfiguration: {
APNSMessage: {
Action: 'OPEN_APP',
Title: "Hi, I am AWS Pinpoint.",
SilentPush: false,
Body: "You've got a nice message."
}
}
}
};
pinpoint.sendUsersMessages(params, (err, data)=>{
if(err){
done(err);
}else{
done(null, data);
}
});
};
Hope those work for you too.
This is certainly possible with Amazon Pinpoint. You can find the Javascript SDK documentation here.
There are 2 modes of sending with Pinpoint.
Direct send - This is effectively the same as what SNS has traditionally been. You need a device token and you can send directly to your push provider using that token.
Segmentation sends - This mode is slightly different and assumes that you have loaded all your devices into Pinpoint via the Mobile SDK as part of your app, or via an S3 Import. The benefit here is that you can segment your devices and send to that segment (e.g. 'Ryans Friends').
So in your Lambda backed API you can choose to either send directly to subscribers (if you have their addresses) or potentially an entire segment of subscribers (if you have loaded your endpoints into Pinpoint).

Resources